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?
|
include? '--homebrew-developer' or !ENV['HOMEBREW_DEVELOPER'].nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sandbox?
|
||||||
|
include?("--sandbox") || !ENV["HOMEBREW_SANDBOX"].nil?
|
||||||
|
end
|
||||||
|
|
||||||
def ignore_deps?
|
def ignore_deps?
|
||||||
include? '--ignore-dependencies'
|
include? '--ignore-dependencies'
|
||||||
end
|
end
|
||||||
|
|||||||
@ -13,6 +13,7 @@ require 'hooks/bottles'
|
|||||||
require 'debrew'
|
require 'debrew'
|
||||||
require 'fcntl'
|
require 'fcntl'
|
||||||
require 'socket'
|
require 'socket'
|
||||||
|
require 'sandbox'
|
||||||
|
|
||||||
class FormulaInstaller
|
class FormulaInstaller
|
||||||
include FormulaCellarChecks
|
include FormulaCellarChecks
|
||||||
@ -484,7 +485,12 @@ class FormulaInstaller
|
|||||||
server.close
|
server.close
|
||||||
read.close
|
read.close
|
||||||
write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
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
|
rescue Exception => e
|
||||||
Marshal.dump(e, write)
|
Marshal.dump(e, write)
|
||||||
write.close
|
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