Skip to content

Commit

Permalink
[rubygems/rubygems] Better error when having an insecure install folder
Browse files Browse the repository at this point in the history
  • Loading branch information
deivid-rodriguez authored and hsbt committed Nov 8, 2023
1 parent 05ea3bc commit a131ea3
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 31 deletions.
8 changes: 0 additions & 8 deletions lib/bundler.rb
Expand Up @@ -331,14 +331,6 @@ def tmp(name = Process.pid.to_s)

def rm_rf(path)
FileUtils.remove_entry_secure(path) if path && File.exist?(path)
rescue ArgumentError
message = <<EOF
It is a security vulnerability to allow your home directory to be world-writable, and bundler cannot continue.
You should probably consider fixing this issue by running `chmod o-w ~` on *nix.
Please refer to https://ruby-doc.org/stdlib-3.1.2/libdoc/fileutils/rdoc/FileUtils.html#method-c-remove_entry_secure for details.
EOF
File.world_writable?(path) ? Bundler.ui.warn(message) : raise
raise PathError, "Please fix the world-writable issue with your #{path} directory"
end

def settings
Expand Down
15 changes: 15 additions & 0 deletions lib/bundler/errors.rb
Expand Up @@ -215,4 +215,19 @@ def initialize(orig_exception, msg)

status_code(36)
end

class InsecureInstallPathError < BundlerError
def initialize(path)
@path = path
end

def message
"The installation path is insecure. Bundler cannot continue.\n" \
"#{@path} is world-writable (without sticky bit).\n" \
"Bundler cannot safely replace gems in world-writeable directories due to potential vulnerabilities.\n" \
"Please change the permissions of this directory or choose a different install path."
end

status_code(38)
end
end
2 changes: 1 addition & 1 deletion lib/bundler/installer/gem_installer.rb
Expand Up @@ -17,7 +17,7 @@ def install_from_spec
Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}"
generate_executable_stubs
[true, post_install_message]
rescue Bundler::InstallHookError, Bundler::SecurityError, Bundler::APIResponseMismatchError
rescue Bundler::InstallHookError, Bundler::SecurityError, Bundler::APIResponseMismatchError, Bundler::InsecureInstallPathError
raise
rescue Errno::ENOSPC
[false, out_of_space_message]
Expand Down
19 changes: 15 additions & 4 deletions lib/bundler/rubygems_gem_installer.rb
Expand Up @@ -124,11 +124,22 @@ def prepare_extension_build(extension_dir)
end

def strict_rm_rf(dir)
Bundler.rm_rf dir
rescue StandardError => e
raise unless File.exist?(dir)
return unless File.exist?(dir)

raise DirectoryRemovalError.new(e, "Could not delete previous installation of `#{dir}`")
parent = File.dirname(dir)
parent_st = File.stat(parent)

if parent_st.world_writable? && !parent_st.sticky?
raise InsecureInstallPathError.new(parent)
end

begin
FileUtils.remove_entry_secure(dir)
rescue StandardError => e
raise unless File.exist?(dir)

raise DirectoryRemovalError.new(e, "Could not delete previous installation of `#{dir}`")
end
end
end
end
18 changes: 0 additions & 18 deletions spec/bundler/bundler/bundler_spec.rb
Expand Up @@ -224,24 +224,6 @@
end
end

describe "#rm_rf" do
context "the directory is world writable" do
let(:bundler_ui) { Bundler.ui }
it "should raise a friendly error" do
allow(File).to receive(:exist?).and_return(true)
allow(::Bundler::FileUtils).to receive(:remove_entry_secure).and_raise(ArgumentError)
allow(File).to receive(:world_writable?).and_return(true)
message = <<EOF
It is a security vulnerability to allow your home directory to be world-writable, and bundler cannot continue.
You should probably consider fixing this issue by running `chmod o-w ~` on *nix.
Please refer to https://ruby-doc.org/stdlib-3.1.2/libdoc/fileutils/rdoc/FileUtils.html#method-c-remove_entry_secure for details.
EOF
expect(bundler_ui).to receive(:warn).with(message)
expect { Bundler.send(:rm_rf, bundled_app) }.to raise_error(Bundler::PathError)
end
end
end

describe "#mkdir_p" do
it "creates a folder at the given path" do
install_gemfile <<-G
Expand Down
30 changes: 30 additions & 0 deletions spec/bundler/commands/install_spec.rb
Expand Up @@ -827,6 +827,36 @@ def run
end
end

describe "when gems path is world writable (no sticky bit set)", :permissions do
let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") }

before do
build_repo4 do
build_gem "foo", "1.0.0" do |s|
s.write "CHANGELOG.md", "foo"
end
end

gemfile <<-G
source "#{file_uri_for(gem_repo4)}"
gem 'foo'
G
end

it "should display a proper message to explain the problem" do
bundle "config set --local path vendor"
bundle :install
expect(out).to include("Bundle complete!")
expect(err).to be_empty

FileUtils.chmod(0o777, gems_path)

bundle "install --redownload", :raise_on_error => false

expect(err).to include("The installation path is insecure. Bundler cannot continue.")
end
end

describe "when bundle cache path does not have write access", :permissions do
let(:cache_path) { bundled_app("vendor/#{Bundler.ruby_scope}/cache") }

Expand Down

0 comments on commit a131ea3

Please sign in to comment.