Skip to content
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

Merged
merged 1 commit into from Aug 17, 2023

Conversation

martinemde
Copy link
Member

@martinemde martinemde commented Aug 15, 2023

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 raises EOFError or returns nil appropriately based on Ruby core IO.read and IO.readpartial behavior.

Zlib handles EOFError and nil reads by raising Zlib::GzipFile::Error.

When verifying a gem or extracting contents, rescue these expected EOFErrors and Zlib errors to return appropriate FormatError for a corrupt gem.

Started as a fix for a bug where size was called on nil because TarReader didn't handle unexpected nil returned from IO.read.

Make sure the following tasks are checked

@martinemde martinemde changed the title Raise Gem::Package::FormatError when gem reaches corrupt EOF Raise Gem::Package::FormatError when gem encounters corrupt EOF Aug 15, 2023
@@ -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?
Copy link
Member Author

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.

lib/rubygems/package.rb Outdated Show resolved Hide resolved
check_closed

return nil if @read >= @header.size
return nil if eof?
Copy link
Member Author

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.

Comment on lines +167 to +168
if ret.nil?
return maxlen ? nil : "" # IO.read returns nil on EOF with len argument
Copy link
Member Author

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.

Comment on lines 233 to 230
size_read = @io.read([pending, 4096].min).size
raise UnexpectedEOF if @io.eof?
ret = @io.readpartial([pending, 4096].min)
size_read = ret.size
Copy link
Member Author

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)
Copy link
Member Author

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.

package.extract_files @destination
end

assert_equal "end of file reached in corrupt.gem", e.message
Copy link
Member Author

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?

@martinemde
Copy link
Member Author

Looks like the errors are legitimate. I'll work on them today.

@martinemde martinemde force-pushed the martinemde/test-corrupt-gem-archives branch from 128c759 to 9323ad1 Compare August 16, 2023 23:58
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.
@martinemde martinemde force-pushed the martinemde/test-corrupt-gem-archives branch from efc7c52 to 0b1d9fc Compare August 17, 2023 20:27
@martinemde martinemde enabled auto-merge (rebase) August 17, 2023 20:28
@martinemde martinemde merged commit dc61296 into master Aug 17, 2023
83 checks passed
@martinemde martinemde deleted the martinemde/test-corrupt-gem-archives branch August 17, 2023 23:16
nobu added a commit to nobu/rubygems that referenced this pull request Aug 19, 2023
@nobu nobu mentioned this pull request Aug 19, 2023
4 tasks
nobu added a commit to nobu/rubygems that referenced this pull request Aug 20, 2023
nobu added a commit to nobu/rubygems that referenced this pull request Aug 20, 2023
@nobu nobu mentioned this pull request Aug 20, 2023
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

NoMethodError: undefined method `size' for nil:NilClass
3 participants