preliminary write control only sandbox
Closes Homebrew/homebrew#38361. Signed-off-by: Xu Cheng <xucheng@me.com>
This commit is contained in:
parent
c86166c43e
commit
2f529220e7
@ -98,6 +98,10 @@ module HomebrewArgvExtension
|
||||
include? '--homebrew-developer' or !ENV['HOMEBREW_DEVELOPER'].nil?
|
||||
end
|
||||
|
||||
def sandbox?
|
||||
include?("--sandbox") || !ENV["HOMEBREW_SANDBOX"].nil?
|
||||
end
|
||||
|
||||
def ignore_deps?
|
||||
include? '--ignore-dependencies'
|
||||
end
|
||||
|
||||
@ -13,6 +13,7 @@ require 'hooks/bottles'
|
||||
require 'debrew'
|
||||
require 'fcntl'
|
||||
require 'socket'
|
||||
require 'sandbox'
|
||||
|
||||
class FormulaInstaller
|
||||
include FormulaCellarChecks
|
||||
@ -484,7 +485,12 @@ class FormulaInstaller
|
||||
server.close
|
||||
read.close
|
||||
write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
||||
exec(*args)
|
||||
if Sandbox.available? && ARGV.sandbox?
|
||||
sandbox = Sandbox.new(formula)
|
||||
sandbox.exec(*args)
|
||||
else
|
||||
exec(*args)
|
||||
end
|
||||
rescue Exception => e
|
||||
Marshal.dump(e, write)
|
||||
write.close
|
||||
|
||||
95
Library/Homebrew/sandbox.rb
Normal file
95
Library/Homebrew/sandbox.rb
Normal file
@ -0,0 +1,95 @@
|
||||
require "erb"
|
||||
require "tempfile"
|
||||
|
||||
class Sandbox
|
||||
SANDBOX_EXEC = "/usr/bin/sandbox-exec".freeze
|
||||
|
||||
def self.available?
|
||||
OS.mac? && File.executable?(SANDBOX_EXEC)
|
||||
end
|
||||
|
||||
def initialize(formula=nil)
|
||||
@profile = SandboxProfile.new
|
||||
unless formula.nil?
|
||||
allow_write "/private/tmp", :type => :subpath
|
||||
allow_write "/private/var/folders", :type => :subpath
|
||||
allow_write HOMEBREW_TEMP, :type => :subpath
|
||||
allow_write HOMEBREW_LOGS/formula.name, :type => :subpath
|
||||
allow_write HOMEBREW_CACHE, :type => :subpath
|
||||
allow_write formula.rack, :type => :subpath
|
||||
allow_write formula.etc, :type => :subpath
|
||||
allow_write formula.var, :type => :subpath
|
||||
end
|
||||
end
|
||||
|
||||
def allow_write(path, options={})
|
||||
case options[:type]
|
||||
when :regex then filter = "regex \#\"#{path}\""
|
||||
when :subpath then filter = "subpath \"#{expand_realpath(Pathname.new(path))}\""
|
||||
when :literal, nil then filter = "literal \"#{expand_realpath(Pathname.new(path))}\""
|
||||
end
|
||||
@profile.add_rule :allow => true,
|
||||
:operation => "file-write*",
|
||||
:filter => filter
|
||||
end
|
||||
|
||||
def exec(*args)
|
||||
begin
|
||||
seatbelt = Tempfile.new(["homebrew", ".sb"], HOMEBREW_TEMP)
|
||||
seatbelt.write(@profile.dump)
|
||||
seatbelt.close
|
||||
safe_system SANDBOX_EXEC, "-f", seatbelt.path, *args
|
||||
rescue
|
||||
if ARGV.verbose?
|
||||
ohai "Sandbox profile:"
|
||||
puts @profile.dump
|
||||
end
|
||||
raise
|
||||
ensure
|
||||
seatbelt.unlink
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def expand_realpath(path)
|
||||
raise unless path.absolute?
|
||||
path.exist? ? path.realpath : expand_realpath(path.parent)/path.basename
|
||||
end
|
||||
|
||||
class SandboxProfile
|
||||
SEATBELT_ERB = <<-EOS.undent
|
||||
(version 1)
|
||||
(debug deny) ; log all denied operations to /var/log/system.log
|
||||
<%= rules.join("\n") %>
|
||||
(allow file-write*
|
||||
(literal "/dev/dtracehelper")
|
||||
(literal "/dev/null")
|
||||
(regex #"^/dev/fd/\\d+$")
|
||||
(regex #"^/dev/tty\\d*$")
|
||||
)
|
||||
(deny file-write*) ; deny non-whitelist file write operations
|
||||
(allow default) ; allow everything else
|
||||
EOS
|
||||
|
||||
attr_reader :rules
|
||||
|
||||
def initialize
|
||||
@rules = []
|
||||
end
|
||||
|
||||
def add_rule(rule)
|
||||
s = "("
|
||||
s << (rule[:allow] ? "allow": "deny")
|
||||
s << " #{rule[:operation]}"
|
||||
s << " (#{rule[:filter]})" if rule[:filter]
|
||||
s << " (with #{rule[:modifier]})" if rule[:modifier]
|
||||
s << ")"
|
||||
@rules << s
|
||||
end
|
||||
|
||||
def dump
|
||||
ERB.new(SEATBELT_ERB).result(binding)
|
||||
end
|
||||
end
|
||||
end
|
||||
28
Library/Homebrew/test/test_sandbox.rb
Normal file
28
Library/Homebrew/test/test_sandbox.rb
Normal file
@ -0,0 +1,28 @@
|
||||
require "testing_env"
|
||||
require "sandbox"
|
||||
|
||||
class SandboxTest < Homebrew::TestCase
|
||||
def setup
|
||||
skip "sandbox not implemented" unless Sandbox.available?
|
||||
end
|
||||
|
||||
def test_allow_write
|
||||
s = Sandbox.new
|
||||
testpath = Pathname.new(TEST_TMPDIR)
|
||||
foo = testpath/"foo"
|
||||
s.allow_write "#{testpath}", :type => :subpath
|
||||
s.exec "touch", foo
|
||||
assert_predicate foo, :exist?
|
||||
foo.unlink
|
||||
end
|
||||
|
||||
def test_deny_write
|
||||
s = Sandbox.new
|
||||
testpath = Pathname.new(TEST_TMPDIR)
|
||||
bar = testpath/"bar"
|
||||
shutup do
|
||||
assert_raises(ErrorDuringExecution) { s.exec "touch", bar }
|
||||
end
|
||||
refute_predicate bar, :exist?
|
||||
end
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user