[ruby/rubygems] Validate spec name before writing to the spec cache

Gem::Source#fetch_spec built the local spec cache path directly from the
name tuple returned by a remote index, which is never validated for use
as a path component. A crafted gem name containing path separators or
`..` could therefore make fetch_spec write the downloaded gemspec bytes
outside Gem.spec_cache_dir. Reject any spec name that is not a plain
basename before constructing the cache path.

https://github.com/ruby/rubygems/commit/56ed326cb2

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hiroshi SHIBATA 2026-07-14 17:27:53 +09:00 committed by git
parent 6a952923e3
commit 57b8f1857f
2 changed files with 20 additions and 0 deletions

View File

@ -109,6 +109,13 @@ class Gem::Source
spec_file_name = name_tuple.spec_name
# The name tuple comes from a remote index and is not otherwise
# validated, so refuse anything that would escape the spec cache
# directory when used as a path component.
if File.basename(spec_file_name) != spec_file_name
raise Gem::Exception, "malformed spec name: #{spec_file_name.inspect}"
end
source_uri = enforce_trailing_slash(uri) + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}"
cache_dir = cache_dir source_uri

View File

@ -121,6 +121,19 @@ class TestGemSource < Gem::TestCase
assert_equal @specs["a-1"].full_name, spec.full_name
end
def test_fetch_spec_path_traversal
escape = File.expand_path(File.join(Gem.spec_cache_dir, "..", "owned.gemspec"))
name_tuple = tuple("../owned", Gem::Version.new(1), "ruby")
e = assert_raise Gem::Exception do
@source.fetch_spec name_tuple
end
assert_includes e.message, "malformed spec name"
refute File.exist?(escape), "spec must not be written outside the spec cache"
end
def test_load_specs
released = @source.load_specs(:released).map(&:full_name)
assert_equal %W[a-2 a-1 b-2], released