From f270aa3eda535f3eb3f77912cf4a7a80f240b89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Sat, 17 Dec 2022 08:47:52 +0100 Subject: [rubygems/rubygems] Use safe Marshal deserialization for dependency API response. - adds Bundler#safe_load_marshal and Bundler::SAFE_MARSHAL_CLASSES listing safe classes to deserialize https://github.com/rubygems/rubygems/commit/e947c608cc --- lib/bundler.rb | 18 ++++++++++++++++-- lib/bundler/fetcher/dependency.rb | 2 +- spec/bundler/bundler/bundler_spec.rb | 20 ++++++++++++++++++++ spec/bundler/bundler/fetcher/dependency_spec.rb | 2 +- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/lib/bundler.rb b/lib/bundler.rb index 658dda3871..5ae8b6120a 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -39,6 +39,16 @@ module Bundler environment_preserver.replace_with_backup SUDO_MUTEX = Thread::Mutex.new + SAFE_MARSHAL_CLASSES = [Symbol, TrueClass, String, Array, Hash].freeze + SAFE_MARSHAL_ERROR = "Unexpected class %s present in marshaled data. Only %s are allowed.".freeze + SAFE_MARSHAL_PROC = proc do |object| + object.tap do + unless SAFE_MARSHAL_CLASSES.include?(object.class) + raise TypeError, format(SAFE_MARSHAL_ERROR, object.class, SAFE_MARSHAL_CLASSES.join(", ")) + end + end + end + autoload :Definition, File.expand_path("bundler/definition", __dir__) autoload :Dependency, File.expand_path("bundler/dependency", __dir__) autoload :Deprecate, File.expand_path("bundler/deprecate", __dir__) @@ -511,8 +521,12 @@ EOF end end - def load_marshal(data) - Marshal.load(data) + def safe_load_marshal(data) + load_marshal(data, :marshal_proc => SAFE_MARSHAL_PROC) + end + + def load_marshal(data, marshal_proc: nil) + Marshal.load(data, marshal_proc) rescue TypeError => e raise MarshalError, "#{e.class}: #{e.message}" end diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb index c52c32fb5b..332f86139d 100644 --- a/lib/bundler/fetcher/dependency.rb +++ b/lib/bundler/fetcher/dependency.rb @@ -55,7 +55,7 @@ module Bundler gem_list = [] gem_names.each_slice(Source::Rubygems::API_REQUEST_SIZE) do |names| marshalled_deps = downloader.fetch(dependency_api_uri(names)).body - gem_list.concat(Bundler.load_marshal(marshalled_deps)) + gem_list.concat(Bundler.safe_load_marshal(marshalled_deps)) end gem_list end diff --git a/spec/bundler/bundler/bundler_spec.rb b/spec/bundler/bundler/bundler_spec.rb index 764c55a91f..5894b4aa12 100644 --- a/spec/bundler/bundler/bundler_spec.rb +++ b/spec/bundler/bundler/bundler_spec.rb @@ -4,6 +4,26 @@ require "bundler" require "tmpdir" RSpec.describe Bundler do + describe "#load_marshal" do + it "loads any data" do + data = Marshal.dump(Bundler) + expect(Bundler.load_marshal(data)).to eq(Bundler) + end + end + + describe "#safe_load_marshal" do + it "fails on unexpected class" do + data = Marshal.dump(Bundler) + expect { Bundler.safe_load_marshal(data) }.to raise_error(Bundler::MarshalError) + end + + it "loads simple structure" do + simple_structure = { "name" => [:abc] } + data = Marshal.dump(simple_structure) + expect(Bundler.safe_load_marshal(data)).to eq(simple_structure) + end + end + describe "#load_gemspec_uncached" do let(:app_gemspec_path) { tmp("test.gemspec") } subject { Bundler.load_gemspec_uncached(app_gemspec_path) } diff --git a/spec/bundler/bundler/fetcher/dependency_spec.rb b/spec/bundler/bundler/fetcher/dependency_spec.rb index 53249116cd..7cfc86ef76 100644 --- a/spec/bundler/bundler/fetcher/dependency_spec.rb +++ b/spec/bundler/bundler/fetcher/dependency_spec.rb @@ -222,7 +222,7 @@ RSpec.describe Bundler::Fetcher::Dependency do it "should fetch dependencies from RubyGems and unmarshal them" do expect(gem_names).to receive(:each_slice).with(rubygems_limit).and_call_original expect(downloader).to receive(:fetch).with(dep_api_uri).and_return(fetch_response) - expect(Bundler).to receive(:load_marshal).with(fetch_response.body).and_return([unmarshalled_gems]) + expect(Bundler).to receive(:safe_load_marshal).with(fetch_response.body).and_return([unmarshalled_gems]) expect(subject.unmarshalled_dep_gems(gem_names)).to eq([unmarshalled_gems]) end end -- cgit v1.2.3