-
-
Notifications
You must be signed in to change notification settings - Fork 2k
[RubyGemsGemInstaller] Validate checksums from the compact index #4851
Changes from all commits
689aff2
df5ad43
3cdccbf
24dbee0
dc2a61c
ae465eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,5 +12,41 @@ def self.at(*args) | |
def check_executable_overwrite(filename) | ||
# Bundler needs to install gems regardless of binstub overwriting | ||
end | ||
|
||
def pre_install_checks | ||
super && validate_bundler_checksum(options[:bundler_expected_checksum]) | ||
end | ||
|
||
private | ||
|
||
def validate_bundler_checksum(checksum) | ||
return true if Bundler.settings[:disable_checksum_validation] | ||
return true unless checksum | ||
return true unless source = @package.instance_variable_get(:@gem) | ||
return true unless source.respond_to?(:with_read_io) | ||
digest = source.with_read_io do |io| | ||
digest = Digest::SHA256.new | ||
digest << io.read(16_384) until io.eof? | ||
io.rewind | ||
digest.send(checksum_type(checksum)) | ||
end | ||
unless digest == checksum | ||
raise SecurityError, | ||
"The checksum for the downloaded `#{spec.full_name}.gem` did not match " \ | ||
"the checksum given by the API. This means that the contents of the " \ | ||
"gem appear to be different from what was uploaded, and could be an indicator of a security issue.\n" \ | ||
"(The expected SHA256 checksum was #{checksum.inspect}, but the checksum for the downloaded gem was #{digest.inspect}.)\n" \ | ||
"Bundler cannot continue installing #{spec.name} (#{spec.version})." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on the principle of empowering users to resolve their own errors, how do you feel about also printing the full path, suggesting deleting the gem with the bad checksum, and explaining that if they are sure they want to install this gem despite the checksum not matching, they can |
||
end | ||
true | ||
end | ||
|
||
def checksum_type(checksum) | ||
case checksum.length | ||
when 64 then :hexdigest! | ||
when 44 then :base64digest! | ||
else raise InstallError, "The given checksum for #{spec.full_name} (#{checksum.inspect}) is not a valid SHA256 hexdigest nor base64digest" | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,7 +78,12 @@ def gems(gem_repo = GEM_REPO) | |
reqs = d.requirement.requirements.map {|r| r.join(" ") }.join(", ") | ||
CompactIndex::Dependency.new(d.name, reqs) | ||
end | ||
CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, nil, nil, | ||
checksum = begin | ||
Digest::SHA256.file("#{GEM_REPO}/gems/#{spec.original_name}.gem").base64digest | ||
rescue | ||
nil | ||
end | ||
CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like the modifier There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how about |
||
deps, spec.required_ruby_version, spec.required_rubygems_version) | ||
end | ||
CompactIndex::Gem.new(name, gem_versions) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# frozen_string_literal: true | ||
require File.expand_path("../compact_index", __FILE__) | ||
|
||
Artifice.deactivate | ||
|
||
class CompactIndexWrongGemChecksum < CompactIndexAPI | ||
get "/info/:name" do | ||
etag_response do | ||
name = params[:name] | ||
gem = gems.find {|g| g.name == name } | ||
checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "ab" * 22 } | ||
versions = gem ? gem.versions : [] | ||
versions.each {|v| v.checksum = checksum } | ||
CompactIndex.info(versions) | ||
end | ||
end | ||
end | ||
|
||
Artifice.activate_with(CompactIndexWrongGemChecksum) |
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.
Can we make the setting
disable.checksum_validation
?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.
none of the other bool keys have dots in them, and wouldn't that break doing the setting as an env var?