Skip to content

Commit

Permalink
Prevent infinite loops during symlink traversal
Browse files Browse the repository at this point in the history
#8815 introduced a traversal
strategy that used recursion.
#9703 then fixed an issue with
this traversal which accounted for directories and symlinks.

When a symlink points to a parent directory that contains that symlink
it'll cause this to go into a loop until the filename is too long for
glob to handle.

We prevent this by checking for the inclusion of a symlink's real path
in the base directory's realpath. If the base directory's path starts
with the symlink's destination then we are in a loop and should skip
processing the directory
  • Loading branch information
Tonkpils authored and bbatsov committed May 3, 2021
1 parent 2c39d51 commit 22274c6
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog/fix_prevent_infinite_loops_during_symlink.md
@@ -0,0 +1 @@
* [#9748](https://github.com/rubocop/rubocop/pull/9748): Prevent infinite loops during symlink traversal. ([@Tonkpils][])
11 changes: 9 additions & 2 deletions lib/rubocop/target_finder.rb
Expand Up @@ -100,12 +100,19 @@ def wanted_dir_patterns(base_dir, exclude_pattern, flags)
next true if dir.end_with?('/./', '/../')
next true if File.fnmatch?(exclude_pattern, dir, flags)

File.symlink?(dir.chomp('/')) && File.fnmatch?(exclude_pattern,
"#{File.realpath(dir)}/", flags)
symlink_excluded_or_infinite_loop?(base_dir, dir, exclude_pattern, flags)
end
dirs.flat_map { |dir| wanted_dir_patterns(dir, exclude_pattern, flags) }.unshift(base_dir)
end

def symlink_excluded_or_infinite_loop?(base_dir, current_dir, exclude_pattern, flags)
dir_realpath = File.realpath(current_dir)
File.symlink?(current_dir.chomp('/')) && (
File.fnmatch?(exclude_pattern, "#{dir_realpath}/", flags) ||
File.realpath(base_dir).start_with?(dir_realpath)
)
end

def combined_exclude_glob_patterns(base_dir)
exclude = @config_store.for(base_dir).for_all_cops['Exclude']
patterns = exclude.select { |pattern| pattern.is_a?(String) && pattern.end_with?('/**/*') }
Expand Down
6 changes: 6 additions & 0 deletions spec/rubocop/target_finder_spec.rb
Expand Up @@ -448,6 +448,12 @@
end
end

it 'prevents infinite loops when traversing symlinks' do
create_link('dir1/link/', File.expand_path('dir1'))

expect(found_basenames).to include('ruby1.rb').once
end

it 'resolves symlinks when looking for excluded directories' do
create_link('link', 'dir1')

Expand Down

0 comments on commit 22274c6

Please sign in to comment.