Skip to content
Snippets Groups Projects
Commit 2f529220 authored by Xu Cheng's avatar Xu Cheng
Browse files

preliminary write control only sandbox


Closes Homebrew/homebrew#38361.

Signed-off-by: default avatarXu Cheng <xucheng@me.com>
parent c86166c4
No related branches found
No related tags found
No related merge requests found
......@@ -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
......
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
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment