summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorSamuel Giddins <segiddins@segiddins.me>2024-04-11 00:05:42 -0700
committergit <svn-admin@ruby-lang.org>2024-04-30 15:34:48 +0000
commitd950609ec709c7c4dc48603b9b2d88f840a520fb (patch)
tree2da7c1649e163787a16d702e419f1d2b3df785c4 /lib
parente0949c3f7cbf32d46ee276d69343b7cb8da4325f (diff)
[rubygems/rubygems] Add a limit to the size of the metadata and checksums files in a gem package.
This is to prevent a malicious gem from causing a denial of service by including a very large metadata or checksums file, which is then read into memory in its entirety just by opening the gem package. This is guaranteed to limit the amount of memory needed, since gzips (which use deflate streams for compression) have a maximum compression ratio of 1032:1, so the uncompressed size of the metadata or checksums file will be at most 1032 times the size of the (limited) amount of data read. This prevents a gem from causing 500GB of memory to be allocated to read a 500MB metadata file. https://github.com/rubygems/rubygems/commit/a596e3c5ec
Diffstat (limited to 'lib')
-rw-r--r--lib/rubygems/package.rb17
1 files changed, 12 insertions, 5 deletions
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index 86d2d90183..c1f31b600e 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -527,12 +527,13 @@ EOM
# Loads a Gem::Specification from the TarEntry +entry+
def load_spec(entry) # :nodoc:
+ limit = 10 * 1024 * 1024
case entry.full_name
when "metadata" then
- @spec = Gem::Specification.from_yaml entry.read
+ @spec = Gem::Specification.from_yaml limit_read(entry, "metadata", limit)
when "metadata.gz" then
Zlib::GzipReader.wrap(entry, external_encoding: Encoding::UTF_8) do |gzio|
- @spec = Gem::Specification.from_yaml gzio.read
+ @spec = Gem::Specification.from_yaml limit_read(gzio, "metadata.gz", limit)
end
end
end
@@ -556,7 +557,7 @@ EOM
@checksums = gem.seek "checksums.yaml.gz" do |entry|
Zlib::GzipReader.wrap entry do |gz_io|
- Gem::SafeYAML.safe_load gz_io.read
+ Gem::SafeYAML.safe_load limit_read(gz_io, "checksums.yaml.gz", 10 * 1024 * 1024)
end
end
end
@@ -663,7 +664,7 @@ EOM
case file_name
when /\.sig$/ then
- @signatures[$`] = entry.read if @security_policy
+ @signatures[$`] = limit_read(entry, file_name, 1024 * 1024) if @security_policy
return
else
digest entry
@@ -714,7 +715,7 @@ EOM
raise Gem::Package::FormatError.new(e.message, entry.full_name)
end
- if RUBY_ENGINE == "truffleruby"
+ if RUBY_ENGINE == "truffleruby" && RUBY_ENGINE_VERSION < "23.1.2"
def copy_stream(src, dst) # :nodoc:
dst.write src.read
end
@@ -723,6 +724,12 @@ EOM
IO.copy_stream(src, dst)
end
end
+
+ def limit_read(io, name, limit)
+ bytes = io.read(limit + 1)
+ raise Gem::Package::FormatError, "#{name} is too big (over #{limit} bytes)" if bytes.size > limit
+ bytes
+ end
end
require_relative "package/digest_io"