Skip to content

Commit

Permalink
Refactor usage of Dir[] to eliminate unsafe glob construction
Browse files Browse the repository at this point in the history
Contructing globs by concatenation through File.join or string interpolation is
susceptible to errors caused by paths inadvertently including glob characters.
This commit fixes this by using Dir.chdir to change the current directory to a
safe point from which to execute the Dir.glob and then reconstructs the path.

There are no specific tests that have been added but the temporary test
directory name has been altered to include the {} characters. This causes a
significant number of the existing tests to fail which should prevent any
regressions.
  • Loading branch information
pixeltrix committed May 16, 2012
1 parent 8f39d56 commit 32326ea
Show file tree
Hide file tree
Showing 13 changed files with 44 additions and 33 deletions.
25 changes: 19 additions & 6 deletions lib/rubygems.rb
Expand Up @@ -261,7 +261,7 @@ def self.all_load_paths
# Return all the partial paths in +gemdir+.

def self.all_partials(gemdir)
Dir[File.join(gemdir, "gems/*")]
Gem.glob(gemdir, "gems/*")
end

private_class_method :all_partials
Expand Down Expand Up @@ -469,8 +469,8 @@ def self.find_files(glob, check_load_path=true)

if check_load_path
files = $LOAD_PATH.map { |load_path|
Dir["#{File.expand_path glob, load_path}#{Gem.suffix_pattern}"]
}.flatten.select { |file| File.file? file.untaint }
Gem.glob(load_path, "#{glob}#{Gem.suffix_pattern}")
}.flatten.select { |file| File.file? file }
end

files.concat Gem::Specification.map { |spec|
Expand Down Expand Up @@ -524,6 +524,21 @@ def self.find_home

private_class_method :find_home

##
# Dir.glob wrapper that takes a base directory

def self.glob(dir, pattern)
# TODO: move to utils
dir = File.expand_path(dir)
return [] unless Dir.exists?(dir)

Dir.chdir dir do
Dir.glob(pattern).map do |filename|
File.join(dir, filename).untaint
end
end
end

##
# Zlib::GzipReader wrapper that unzips +data+.

Expand Down Expand Up @@ -1099,11 +1114,9 @@ def self.load_plugins
# Find all 'rubygems_plugin' files in $LOAD_PATH and load them

def self.load_env_plugins
path = "rubygems_plugin"

files = []
$LOAD_PATH.each do |load_path|
globbed = Dir["#{File.expand_path path, load_path}#{Gem.suffix_pattern}"]
globbed = Gem.glob(load_path, "rubygems_plugin#{Gem.suffix_pattern}")

globbed.each do |load_path_file|
files << load_path_file if File.file?(load_path_file.untaint)
Expand Down
6 changes: 3 additions & 3 deletions lib/rubygems/commands/contents_command.rb
Expand Up @@ -81,9 +81,9 @@ def execute
end

gem_path = spec.full_gem_path
extra = "/{#{spec.require_paths.join ','}}" if options[:lib_only]
glob = "#{gem_path}#{extra}/**/*"
files = Dir[glob]
extra = "{#{spec.require_paths.join ','}}/" if options[:lib_only]
glob = "#{extra}**/*"
files = Gem.glob(gem_path, glob)

gem_path = File.join gem_path, '' # add trailing / if missing

Expand Down
2 changes: 1 addition & 1 deletion lib/rubygems/commands/setup_command.rb
Expand Up @@ -254,7 +254,7 @@ def install_rdoc
(not File.exist? rubygems_doc_dir or
File.writable? rubygems_doc_dir) then
say "Removing old RubyGems RDoc and ri" if @verbose
Dir[File.join(Gem.dir, 'doc', 'rubygems-[0-9]*')].each do |dir|
Gem.glob(File.join(Gem.dir, 'doc'), 'rubygems-[0-9]*').each do |dir|
rm_rf dir
end

Expand Down
2 changes: 1 addition & 1 deletion lib/rubygems/commands/stale_command.rb
Expand Up @@ -13,7 +13,7 @@ def execute
gem_to_atime = {}
Gem::Specification.each do |spec|
name = spec.full_name
Dir["#{spec.full_gem_path}/**/*.*"].each do |file|
Gem.glob(spec.full_gem_path, "**/*.*").each do |file|
next if File.directory?(file)
stat = File.stat(file)
gem_to_atime[name] ||= stat.atime
Expand Down
2 changes: 1 addition & 1 deletion lib/rubygems/indexer.rb
Expand Up @@ -400,7 +400,7 @@ def compress(filename, extension)
# List of gem file names to index.

def gem_file_list
Dir[File.join(@dest_directory, "gems", '*.gem')]
Gem.glob(File.join(@dest_directory, 'gems'), '*.gem')
end

##
Expand Down
4 changes: 2 additions & 2 deletions lib/rubygems/installer.rb
Expand Up @@ -299,8 +299,8 @@ def installed_specs
@specs ||= begin
specs = []

Dir[File.join(gem_home, "specifications", "*.gemspec")].each do |path|
spec = Gem::Specification.load path.untaint
Gem.glob(File.join(gem_home, "specifications"), "*.gemspec").each do |path|
spec = Gem::Specification.load path
specs << spec if spec
end

Expand Down
2 changes: 1 addition & 1 deletion lib/rubygems/request_set.rb
Expand Up @@ -83,7 +83,7 @@ def sorted_requests
end

def specs_in(dir)
Dir["#{dir}/specifications/*.gemspec"].map do |g|
Gem.glob(dir, "specifications/*.gemspec").map do |g|
Gem::Specification.load g
end
end
Expand Down
4 changes: 1 addition & 3 deletions lib/rubygems/security/trust_dir.rb
Expand Up @@ -27,9 +27,7 @@ def cert_path certificate
def each_certificate
return enum_for __method__ unless block_given?

glob = File.join @dir, '*.pem'

Dir[glob].each do |certificate_file|
Gem.glob(@dir, '*.pem').each do |certificate_file|
begin
certificate = load_certificate certificate_file

Expand Down
9 changes: 3 additions & 6 deletions lib/rubygems/specification.rb
Expand Up @@ -632,8 +632,8 @@ def self._all # :nodoc:
specs = {}

self.dirs.each { |dir|
Dir[File.join(dir, "*.gemspec")].each { |path|
spec = Gem::Specification.load path.untaint
Gem.glob(dir, "*.gemspec").each { |path|
spec = Gem::Specification.load path
# #load returns nil if the spec is bad, so we just ignore
# it at this stage
specs[spec.full_name] ||= spec if spec
Expand Down Expand Up @@ -1827,10 +1827,7 @@ def mark_version
# Return all files in this gem that match for +glob+.

def matches_for_glob glob # TODO: rename?
# TODO: do we need these?? Kill it
glob = File.join(self.lib_dirs_glob, glob)

Dir[glob].map { |f| f.untaint } # FIX our tests are broken, run w/ SAFE=1
Gem.glob(full_gem_path, "{#{require_paths.join ','}}/#{glob}")
end

##
Expand Down
8 changes: 4 additions & 4 deletions lib/rubygems/test_case.rb
Expand Up @@ -107,7 +107,7 @@ def refute_path_exists path, msg = nil
# or <tt>i686-darwin8.10.1</tt> otherwise.
#
# If the +KEEP_FILES+ environment variable is set the files will not be
# removed from <tt>/tmp/test_rubygems_#{$$}.#{Time.now.to_i}</tt>.
# removed from <tt>/tmp/test_rubygems_{#{$$}}.#{Time.now.to_i}</tt>.

def setup
super
Expand All @@ -122,9 +122,9 @@ def setup
tmpdir = File.expand_path("tmp/test")

if ENV['KEEP_FILES'] then
@tempdir = File.join(tmpdir, "test_rubygems_#{$$}.#{Time.now.to_i}")
@tempdir = File.join(tmpdir, "test_rubygems_{#{$$}}.#{Time.now.to_i}")
else
@tempdir = File.join(tmpdir, "test_rubygems_#{$$}")
@tempdir = File.join(tmpdir, "test_rubygems_{#{$$}}")
end
@tempdir.untaint
@gemhome = File.join @tempdir, 'gemhome'
Expand Down Expand Up @@ -298,7 +298,7 @@ def uninstall_gem spec
def create_tmpdir
tmpdir = nil
Dir.chdir Dir.tmpdir do tmpdir = Dir.pwd end # HACK OSX /private/tmp
tmpdir = File.join tmpdir, "test_rubygems_#{$$}"
tmpdir = File.join tmpdir, "test_rubygems_{#{$$}}"
FileUtils.mkdir_p tmpdir
return tmpdir
end
Expand Down
4 changes: 2 additions & 2 deletions test/rubygems/test_gem_commands_install_command.rb
Expand Up @@ -391,7 +391,7 @@ def test_execute_remote_ignores_files
FileUtils.rm_rf a1_gemspec
FileUtils.rm_rf a2_gemspec

start = Dir["#{gemdir}/*"]
start = Gem.glob(gemdir, "*")

use_ui @ui do
Dir.chdir @tempdir do
Expand All @@ -408,7 +408,7 @@ def test_execute_remote_ignores_files
assert_equal "1 gem installed", out.shift
assert out.empty?, out.inspect

fin = Dir["#{gemdir}/*"]
fin = Gem.glob(gemdir, "*")

assert_equal [a1_gemspec], fin - start
end
Expand Down
2 changes: 1 addition & 1 deletion test/rubygems/test_gem_indexer.rb
Expand Up @@ -28,7 +28,7 @@ def setup

gems = File.join(@tempdir, 'gems')
FileUtils.mkdir_p gems
FileUtils.mv Dir[File.join(@gemhome, "cache", '*.gem')], gems
FileUtils.mv Gem.glob(File.join(@gemhome, "cache"), '*.gem'), gems

@indexer = Gem::Indexer.new(@tempdir,
:rss_title => 'ExampleForge gems',
Expand Down
7 changes: 5 additions & 2 deletions test/rubygems/test_gem_remote_fetcher.rb
Expand Up @@ -292,8 +292,11 @@ def test_download_local_read_only
inst = Gem::RemoteFetcher.fetcher
end

assert_equal(File.join(@tempdir, @a1.file_name),
inst.download(@a1, local_path))
uri = URI.const_defined?(:DEFAULT_PARSER) ?
URI::DEFAULT_PARSER.escape(File.join(@tempdir, @a1.file_name)) :
URI.escape(File.join(@tempdir, @a1.file_name))

assert_equal(uri, inst.download(@a1, local_path))
ensure
FileUtils.chmod 0755, @a1.cache_dir
end
Expand Down

0 comments on commit 32326ea

Please sign in to comment.