Raise FormatError for a corrupt checksums.yaml.gz in Package#verify - #9702
Raise FormatError for a corrupt checksums.yaml.gz in Package#verify#9702arpitjain099 wants to merge 2 commits into
Conversation
Package#digest builds its digest set from @Checksums, which is loaded straight from the untrusted checksums.yaml.gz inside the .gem. If that YAML root isn't a mapping, digest calls .to_h on it and raises NoMethodError; if a top-level key isn't a known OpenSSL digest name, Gem::Security.create_digest raises OpenSSL::Digest::DigestError. Neither is on verify's rescue list, so verify leaks a raw exception and backtrace instead of the Gem::Package::FormatError it raises for every other corrupt-package case. Older rubygems funneled these through a broad rescue StandardError; the narrowed rescue list dropped that, so this is a regression in the error contract. Guard the checksums structure at the source: reject a non-mapping root and turn an unknown-algorithm DigestError into FormatError, which keeps verify's deliberately narrow rescue intact. Adds two regression tests. Signed-off-by: arpitjain099 <arpitjain099@gmail.com>
|
Please fix the failing tests on CI. |
OpenSSL raises RuntimeError, not OpenSSL::Digest::DigestError, when the digest name is unknown, and the class varies across engines, so the narrower rescue never fired and the raw error escaped Package#verify. That is what failed test_verify_checksum_bad_algorithm on every platform. The algorithm name is read from the gem's own checksums.yaml.gz, so any failure to build a digest from it means the gem is malformed. Only the checksums branch is widened; the internal DIGEST_NAME path is unchanged. Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
|
Fixed in 119b249. The cause was my rescue clause catching the wrong class. So the rescue never fired, the raw error escaped It now rescues One note on verification: my local Ruby is 2.6 and master needs 3.2+, so I could not run the suite here. I confirmed the behaviour directly against the same |
Gem::Package#verifyis documented to raiseGem::Package::FormatErrorwhen a.gemis corrupt, and it maps the corruption errors it knows about (Zlib::GzipFile::Error,EOFError,TarInvalidError,Errno::ENOENT) onto that. Two corrupt-checksums.yaml.gzshapes slip through it as raw exceptions:#digestbuilds its algorithm set with@checksums.to_h { |algorithm, _| ... }.@checksumsis loaded straight fromchecksums.yaml.gz, so if that YAML's root isn't a mapping (a scalar, say),.to_hraisesNoMethodError.Gem::Security.create_digest, i.e.OpenSSL::Digest.new(algorithm). A key that isn't a known digest name raisesOpenSSL::Digest::DigestError.Neither is on
verify's rescue list, so instead ofFormatError "... is corrupt"the caller gets a raw error plus backtrace. Older rubygems funneled these through a broadrescue StandardError; the rescue list was later narrowed to specific classes, which is reasonable but dropped the coverage for these two, so it's a regression in the error contract rather than an ancient wart.This guards the checksums structure at the source in
#digest: reject a non-mapping root and turn an unknown-algorithmDigestErrorintoFormatError. That keepsverify's deliberately narrow rescue list intact instead of widening it back to a catch-all.To be clear on scope: this is only about the error type. The gem is rejected either way; a corrupt/mistyped checksums file never verifies. The fix just makes it fail with the documented
FormatErrorinstead of anOpenSSL/NoMethoderror, which matters for any caller that rescuesFormatErroraroundGem::Package.new(path).verify/.spec(installers, mirrors, SBOM/SCA tooling).Verification: added two regression tests (unknown algorithm name, non-mapping root). Both raise the raw error on
masterandGem::Package::FormatErrorwith the fix.ruby -Itest -Ilib test/rubygems/test_gem_package.rbis green (60 tests, 0 failures) on ruby 4.0.