Skip to content

Commit 4fa3f04

Browse files
authored
Merge pull request #9595 from ruby/strengthen-installer-validation
Add executables and bindir validation to the gem installer
2 parents d2055b4 + 89bf13a commit 4fa3f04

2 files changed

Lines changed: 106 additions & 2 deletions

File tree

lib/rubygems/installer.rb

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,18 @@ def verify_spec
712712
if spec.dependencies.any? {|dep| dep.name =~ /(?:\R|[<>])/ }
713713
raise Gem::InstallError, "#{spec} has an invalid dependencies"
714714
end
715+
716+
if spec.executables.any? {|name| !name.is_a?(String) || name != File.basename(name) || /\A\.\.?\z|\R/.match?(name) }
717+
raise Gem::InstallError, "#{spec} has an invalid executable"
718+
end
719+
720+
raise Gem::InstallError, "#{spec} has an invalid bindir" unless spec.bindir.is_a?(String)
721+
722+
expanded_gem_dir = File.expand_path(gem_dir)
723+
expanded_bindir = File.expand_path(File.join(gem_dir, spec.bindir))
724+
unless expanded_bindir == expanded_gem_dir || expanded_bindir.start_with?("#{expanded_gem_dir}/")
725+
raise Gem::InstallError, "#{spec} has an invalid bindir"
726+
end
715727
end
716728

717729
##
@@ -720,6 +732,7 @@ def verify_spec
720732
def app_script_text(bin_file_name)
721733
# NOTE: that the `load` lines cannot be indented, as old RG versions match
722734
# against the beginning of the line
735+
escaped_bin_file_name = bin_file_name.gsub(/[\\']/) {|c| "\\#{c}" }
723736
<<~TEXT
724737
#{shebang bin_file_name}
725738
#
@@ -743,9 +756,9 @@ def app_script_text(bin_file_name)
743756
end
744757
745758
if Gem.respond_to?(:activate_and_load_bin_path)
746-
Gem.activate_and_load_bin_path('#{spec.name}', '#{bin_file_name}', version)
759+
Gem.activate_and_load_bin_path('#{spec.name}', '#{escaped_bin_file_name}', version)
747760
else
748-
load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version)
761+
load Gem.activate_bin_path('#{spec.name}', '#{escaped_bin_file_name}', version)
749762
end
750763
TEXT
751764
end

test/rubygems/test_gem_installer.rb

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,21 @@ def test_app_script_text
6060
end
6161
end
6262

63+
def test_app_script_text_escapes_executable_name
64+
installer = setup_base_installer
65+
66+
malicious = "evil');system('id');#"
67+
@spec.bindir = "bin"
68+
write_file @spec.bin_file(malicious) do |io|
69+
io.puts "#!/usr/bin/ruby"
70+
end
71+
72+
wrapper = installer.app_script_text malicious
73+
74+
assert_includes wrapper, %q{Gem.activate_and_load_bin_path('a', 'evil\');system(\'id\');#', version)}
75+
assert_includes wrapper, %q{load Gem.activate_bin_path('a', 'evil\');system(\'id\');#', version)}
76+
end
77+
6378
def test_check_executable_overwrite
6479
installer = setup_base_installer
6580

@@ -1954,6 +1969,82 @@ def spec.validate(*args); end
19541969
end
19551970
end
19561971

1972+
def test_pre_install_checks_malicious_executables_before_eval
1973+
spec = util_spec "malicious", "1"
1974+
def spec.full_name # so the spec is buildable
1975+
"malicious-1"
1976+
end
1977+
1978+
def spec.validate(*args); end
1979+
spec.executables = ["../../../tmp/malicious"]
1980+
1981+
util_build_gem spec
1982+
1983+
gem = File.join(@gemhome, "cache", spec.file_name)
1984+
1985+
use_ui @ui do
1986+
installer = Gem::Installer.at gem
1987+
e = assert_raise Gem::InstallError do
1988+
installer.pre_install_checks
1989+
end
1990+
assert_equal "#<Gem::Specification name=malicious version=1> has an invalid executable", e.message
1991+
end
1992+
end
1993+
1994+
def test_pre_install_checks_malicious_bindir_before_eval
1995+
spec = util_spec "malicious", "1"
1996+
def spec.full_name # so the spec is buildable
1997+
"malicious-1"
1998+
end
1999+
2000+
def spec.validate(*args); end
2001+
spec.bindir = "../../../tmp/malicious"
2002+
2003+
util_build_gem spec
2004+
2005+
gem = File.join(@gemhome, "cache", spec.file_name)
2006+
2007+
use_ui @ui do
2008+
installer = Gem::Installer.at gem
2009+
e = assert_raise Gem::InstallError do
2010+
installer.pre_install_checks
2011+
end
2012+
assert_equal "#<Gem::Specification name=malicious version=1> has an invalid bindir", e.message
2013+
end
2014+
end
2015+
2016+
def test_pre_install_checks_non_string_executable
2017+
spec = util_spec "malicious", "1"
2018+
def spec.validate(*args); end
2019+
spec.executables = [nil]
2020+
2021+
installer = Gem::Installer.for_spec spec
2022+
installer.gem_home = @gemhome
2023+
2024+
use_ui @ui do
2025+
e = assert_raise Gem::InstallError do
2026+
installer.pre_install_checks
2027+
end
2028+
assert_equal "#<Gem::Specification name=malicious version=1> has an invalid executable", e.message
2029+
end
2030+
end
2031+
2032+
def test_pre_install_checks_non_string_bindir
2033+
spec = util_spec "malicious", "1"
2034+
def spec.validate(*args); end
2035+
spec.bindir = true
2036+
2037+
installer = Gem::Installer.for_spec spec
2038+
installer.gem_home = @gemhome
2039+
2040+
use_ui @ui do
2041+
e = assert_raise Gem::InstallError do
2042+
installer.pre_install_checks
2043+
end
2044+
assert_equal "#<Gem::Specification name=malicious version=1> has an invalid bindir", e.message
2045+
end
2046+
end
2047+
19572048
def test_pre_install_checks_malicious_platform_before_eval
19582049
gem_with_ill_formatted_platform = File.expand_path("packages/ill-formatted-platform-1.0.0.10.gem", __dir__)
19592050

0 commit comments

Comments
 (0)