summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml19
-rw-r--r--Rakefile2
-rw-r--r--lib/bundler/definition.rb3
-rw-r--r--lib/bundler/resolver.rb8
-rw-r--r--lib/bundler/rubygems_gem_installer.rb36
-rw-r--r--lib/bundler/settings.rb1
-rw-r--r--lib/bundler/source/rubygems.rb3
-rw-r--r--spec/commands/lock_spec.rb91
-rw-r--r--spec/install/gems/compact_index_spec.rb32
-rw-r--r--spec/support/artifice/compact_index.rb7
-rw-r--r--spec/support/artifice/compact_index_wrong_gem_checksum.rb19
-rw-r--r--spec/support/matchers.rb6
12 files changed, 196 insertions, 31 deletions
diff --git a/.travis.yml b/.travis.yml
index 223cce066a..88ae318a94 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -109,23 +109,4 @@ matrix:
allow_failures:
- rvm: 1.8.7
- env: RGV=v2.1.11
- - rvm: 1.8.7
- env: RGV=v2.2.5
- - rvm: 1.8.7
- env: RGV=v2.0.14
- - rvm: 1.8.7
- env: RGV=v1.8.29
- - rvm: 1.8.7
- env: RGV=v1.7.2
- - rvm: 1.8.7
- env: RGV=v1.6.2
- - rvm: 1.8.7
- env: RGV=v1.5.3
- - rvm: 1.8.7
- env: RGV=v1.4.2
- - rvm: 1.8.7
- env: RGV=v1.3.7
- - rvm: 1.8.7
- env: RGV=v1.3.6
- rvm: ruby-head
diff --git a/Rakefile b/Rakefile
index e7b2a3a6ca..cc7162a6ef 100644
--- a/Rakefile
+++ b/Rakefile
@@ -127,7 +127,7 @@ begin
rubyopt = ENV["RUBYOPT"]
# When editing this list, also edit .travis.yml!
branches = %w(master)
- releases = %w(v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.29 v2.0.14 v2.1.11 v2.2.5 v2.4.8 v2.6.6)
+ releases = %w(v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.29 v2.0.14 v2.1.11 v2.2.5 v2.4.8 v2.5.2 v2.6.6)
(branches + releases).each do |rg|
desc "Run specs with Rubygems #{rg}"
RSpec::Core::RakeTask.new(rg) do |t|
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 2d328e0de1..e1826746ff 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -816,7 +816,8 @@ module Bundler
def additional_base_requirements_for_resolve
return [] unless @locked_gems && Bundler.settings[:only_update_to_newer_versions]
@locked_gems.specs.reduce({}) do |requirements, locked_spec|
- requirements[locked_spec.name] = Gem::Dependency.new(locked_spec.name, ">= #{locked_spec.version}")
+ dep = Gem::Dependency.new(locked_spec.name, ">= #{locked_spec.version}")
+ requirements[locked_spec.name] = DepProxy.new(dep, locked_spec.platform)
requirements
end.values
end
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb
index 10d5404028..e1d993831f 100644
--- a/lib/bundler/resolver.rb
+++ b/lib/bundler/resolver.rb
@@ -189,7 +189,10 @@ module Bundler
@resolver = Molinillo::Resolver.new(self, self)
@search_for = {}
@base_dg = Molinillo::DependencyGraph.new
- @base.each {|ls| @base_dg.add_vertex(ls.name, Dependency.new(ls.name, ls.version), true) }
+ @base.each do |ls|
+ dep = Dependency.new(ls.name, ls.version)
+ @base_dg.add_vertex(ls.name, DepProxy.new(dep, ls.platform), true)
+ end
additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) }
@ruby_version = ruby_version
@gem_version_promoter = gem_version_promoter
@@ -303,7 +306,8 @@ module Bundler
end
def requirement_satisfied_by?(requirement, activated, spec)
- requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec)
+ return false unless requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec)
+ spec.activate_platform!(requirement.__platform) || spec.for?(requirement.__platform, nil)
end
def sort_dependencies(dependencies, activated, conflicts)
diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb
index e18f46268b..0aa9fd91d6 100644
--- a/lib/bundler/rubygems_gem_installer.rb
+++ b/lib/bundler/rubygems_gem_installer.rb
@@ -12,5 +12,41 @@ module Bundler
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})."
+ 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
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
index 365d20adda..c5fd46d440 100644
--- a/lib/bundler/settings.rb
+++ b/lib/bundler/settings.rb
@@ -6,6 +6,7 @@ module Bundler
BOOL_KEYS = %w(
allow_offline_install
cache_all
+ disable_checksum_validation
disable_exec_load
disable_local_branch_check
disable_shared_gems
diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb
index aedad7086d..89f7673eb8 100644
--- a/lib/bundler/source/rubygems.rb
+++ b/lib/bundler/source/rubygems.rb
@@ -140,7 +140,8 @@ module Bundler
:bin_dir => bin_path.to_s,
:ignore_dependencies => true,
:wrappers => true,
- :env_shebang => true
+ :env_shebang => true,
+ :bundler_expected_checksum => spec.respond_to?(:checksum) && spec.checksum
).install
end
spec.full_gem_path = installed_spec.full_gem_path
diff --git a/spec/commands/lock_spec.rb b/spec/commands/lock_spec.rb
index 1e189a9659..fc605dc5a6 100644
--- a/spec/commands/lock_spec.rb
+++ b/spec/commands/lock_spec.rb
@@ -132,4 +132,95 @@ describe "bundle lock" do
bundle "lock --remove-platform #{local}"
expect(out).to include("Removing all platforms from the bundle is not allowed")
end
+
+ # from https://github.com/bundler/bundler/issues/4896
+ it "properly adds platforms when platform requirements come from different dependencies" do
+ build_repo4 do
+ build_gem "ffi", "1.9.14"
+ build_gem "ffi", "1.9.14" do |s|
+ s.platform = mingw
+ end
+
+ build_gem "gssapi", "0.1"
+ build_gem "gssapi", "0.2"
+ build_gem "gssapi", "0.3"
+ build_gem "gssapi", "1.2.0" do |s|
+ s.add_dependency "ffi", ">= 1.0.1"
+ end
+
+ build_gem "mixlib-shellout", "2.2.6"
+ build_gem "mixlib-shellout", "2.2.6" do |s|
+ s.platform = "universal-mingw32"
+ s.add_dependency "win32-process", "~> 0.8.2"
+ end
+
+ # we need all these versions to get the sorting the same as it would be
+ # pulling from rubygems.org
+ %w(0.8.3 0.8.2 0.8.1 0.8.0).each do |v|
+ build_gem "win32-process", v do |s|
+ s.add_dependency "ffi", ">= 1.0.0"
+ end
+ end
+ end
+
+ gemfile <<-G
+ source "file:#{gem_repo4}"
+
+ gem "mixlib-shellout"
+ gem "gssapi"
+ G
+
+ simulate_platform(mingw) { bundle! :lock }
+
+ expect(the_bundle.lockfile).to read_as(strip_whitespace(<<-G))
+ GEM
+ remote: file:#{gem_repo4}/
+ specs:
+ ffi (1.9.14-x86-mingw32)
+ gssapi (1.2.0)
+ ffi (>= 1.0.1)
+ mixlib-shellout (2.2.6-universal-mingw32)
+ win32-process (~> 0.8.2)
+ win32-process (0.8.3)
+ ffi (>= 1.0.0)
+
+ PLATFORMS
+ x86-mingw32
+
+ DEPENDENCIES
+ gssapi
+ mixlib-shellout
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ simulate_platform(rb) { bundle! :lock }
+
+ expect(the_bundle.lockfile).to read_as(strip_whitespace(<<-G))
+ GEM
+ remote: file:#{gem_repo4}/
+ specs:
+ ffi (1.9.14)
+ ffi (1.9.14-x86-mingw32)
+ gssapi (1.2.0)
+ ffi (>= 1.0.1)
+ mixlib-shellout (2.2.6)
+ mixlib-shellout (2.2.6-universal-mingw32)
+ win32-process (~> 0.8.2)
+ win32-process (0.8.3)
+ ffi (>= 1.0.0)
+
+ PLATFORMS
+ ruby
+ x86-mingw32
+
+ DEPENDENCIES
+ gssapi
+ mixlib-shellout
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
end
diff --git a/spec/install/gems/compact_index_spec.rb b/spec/install/gems/compact_index_spec.rb
index 0edd1d20e7..a800a6ad7b 100644
--- a/spec/install/gems/compact_index_spec.rb
+++ b/spec/install/gems/compact_index_spec.rb
@@ -695,4 +695,36 @@ The checksum of /versions does not match the checksum provided by the server! So
expect(File.read(versions)).to start_with("created_at")
expect(the_bundle).to include_gems "rack 1.0.0"
end
+
+ describe "checksum validation", :rubygems => ">= 2.3.0" do
+ it "raises when the checksum does not match" do
+ install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum"
+ source "#{source_uri}"
+ gem "rack"
+ G
+ expect(exitstatus).to eq(19) if exitstatus
+ expect(out).
+ to include("The checksum for the downloaded `rack-1.0.0.gem` did not match the checksum given by the API.").
+ and include("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.").
+ and match(/\(The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/).
+ and include("Bundler cannot continue installing rack (1.0.0).")
+ end
+
+ it "raises when the checksum is the wrong length" do
+ install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :env => { "BUNDLER_SPEC_RACK_CHECKSUM" => "checksum!" }
+ source "#{source_uri}"
+ gem "rack"
+ G
+ expect(exitstatus).to eq(5) if exitstatus
+ expect(out).to include("The given checksum for rack-1.0.0 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest")
+ end
+
+ it "does not raise when disable_checksum_validation is set" do
+ bundle! "config disable_checksum_validation true"
+ install_gemfile! <<-G, :artifice => "compact_index_wrong_gem_checksum"
+ source "#{source_uri}"
+ gem "rack"
+ G
+ end
+ end
end
diff --git a/spec/support/artifice/compact_index.rb b/spec/support/artifice/compact_index.rb
index 233c192a67..f3ff2db4fa 100644
--- a/spec/support/artifice/compact_index.rb
+++ b/spec/support/artifice/compact_index.rb
@@ -78,7 +78,12 @@ class CompactIndexAPI < Endpoint
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,
deps, spec.required_ruby_version, spec.required_rubygems_version)
end
CompactIndex::Gem.new(name, gem_versions)
diff --git a/spec/support/artifice/compact_index_wrong_gem_checksum.rb b/spec/support/artifice/compact_index_wrong_gem_checksum.rb
new file mode 100644
index 0000000000..3a12a59ae7
--- /dev/null
+++ b/spec/support/artifice/compact_index_wrong_gem_checksum.rb
@@ -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)
diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb
index 9476f18984..9248360639 100644
--- a/spec/support/matchers.rb
+++ b/spec/support/matchers.rb
@@ -110,15 +110,9 @@ module Spec
define_compound_matcher :read_as, [exist] do |file_contents|
diffable
- attr_reader :strip_whitespace
-
- chain :stripping_whitespace do
- @strip_whitespace = true
- end
match do |actual|
@actual = Bundler.read_file(actual)
- file_contents = strip_whitespace(file_contents) if strip_whitespace
values_match?(file_contents, @actual)
end
end