New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Raise Gem::Package::FormatError when gem encounters corrupt EOF #6882
Conversation
@@ -363,7 +365,7 @@ def digest(entry) # :nodoc: | |||
algorithms.each do |algorithm| | |||
digester = Gem::Security.create_digest(algorithm) | |||
|
|||
digester << entry.read(16_384) until entry.eof? | |||
digester << entry.readpartial(16_384) until entry.eof? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to readpartial to benefit from automatically raising EOFError if the entry is corrupt. Otherwise it tries to shovel nil into the digester.
check_closed | ||
|
||
return nil if @read >= @header.size | ||
return nil if eof? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eof?
performs check_closed. Similar changes below too.
if ret.nil? | ||
return maxlen ? nil : "" # IO.read returns nil on EOF with len argument |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the core change. If io.read returns nil, handle it appropriately depending on if maxlen
was given.
size_read = @io.read([pending, 4096].min).size | ||
raise UnexpectedEOF if @io.eof? | ||
ret = @io.readpartial([pending, 4096].min) | ||
size_read = ret.size |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same problem here where we ignored the possible nil
return from read.
I also removed UnexpectedEOF here. I think there's two problems: If the read above hit an EOF without reading any bytes, then .size
would raise. If it properly read to right up to the EOF, like if this was the last file in the archive, then this would raise when it should not. The only reason to raise here is if we hit a normal EOFError due to readpartial, so I just let readpartial handle raising.
We could remove the UnexpectedEOF constant also. It was only (never) used here, so I think it's safe to remove completely. Besides, all EOFErrors are unexpected. 🤷♂️
@@ -946,6 +946,87 @@ def test_verify_corrupt | |||
tf.close! | |||
end | |||
|
|||
def test_verify_corrupt_tar_metadata_entry | |||
gem = tar_file_header("metadata.gz", "", 0, 999, Time.now) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
verify
handles metadata, checksums, and data.tar.gz slightly differently, raising different errors when each is corrupt. That's why there's so many new tests here for seemingly the same thing.
test/rubygems/test_gem_package.rb
Outdated
package.extract_files @destination | ||
end | ||
|
||
assert_equal "end of file reached in corrupt.gem", e.message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I make the error better? Currently it's just a passthru error message for EOFError or Zlib's "not in gzip format" error. We could be more clear about it being "corrupt". Opinions?
Looks like the errors are legitimate. I'll work on them today. |
128c759
to
9323ad1
Compare
Gem::Package::TarReader::Entry now raises EOFError or returns nil appropriately based on Ruby core IO.read and IO.readpartial behavior. Zlib will respond accordingly by raising Zlib::GzipFile::Error on EOF. When verifying a gem or extracting contents, raise FormatError similar to other cases of corrupt gems. Addresses a bug where Gem::Package would attempt to call size on nil instead of raising a more descriptive and useful error, leading users to assume the problem is internal to rubygems. Remove unused error class TarReader::UnexpectedEOF that was never raised since the NoMethodError on nil would happen first. Use EOFError instead.
efc7c52
to
0b1d9fc
Compare
What was the end-user or developer problem that led to this PR?
Fixes #5981 which raised a
NoMethodError
when the real problem was attempting to read a corrupted gem file.What is your fix for the problem, implemented in this PR?
Gem::Package::TarReader::Entry
raisesEOFError
or returnsnil
appropriately based on Ruby coreIO.read
andIO.readpartial
behavior.Zlib handles
EOFError
andnil
reads by raisingZlib::GzipFile::Error
.When verifying a gem or extracting contents, rescue these expected
EOFError
s and Zlib errors to return appropriate FormatError for a corrupt gem.Started as a fix for a bug where
size
was called onnil
becauseTarReader
didn't handle unexpectednil
returned fromIO.read
.Make sure the following tasks are checked