aboutsummaryrefslogtreecommitdiffstats
path: root/Library
diff options
context:
space:
mode:
authorXu Cheng2015-04-09 17:42:54 +0800
committerXu Cheng2015-04-09 17:42:54 +0800
commite82c3ce71878d2615a29832e6989387587c99a56 (patch)
treef05835d999d860e9c803e28ca97acccb39ee25b6 /Library
parent16387ff98d61f79c8d437952405a08779f41fa1f (diff)
downloadhomebrew-e82c3ce71878d2615a29832e6989387587c99a56.tar.bz2
preliminary write control only sandbox
Closes #38361. Signed-off-by: Xu Cheng <xucheng@me.com>
Diffstat (limited to 'Library')
-rw-r--r--Library/Homebrew/extend/ARGV.rb4
-rw-r--r--Library/Homebrew/formula_installer.rb8
-rw-r--r--Library/Homebrew/sandbox.rb95
-rw-r--r--Library/Homebrew/test/test_sandbox.rb28
4 files changed, 134 insertions, 1 deletions
diff --git a/Library/Homebrew/extend/ARGV.rb b/Library/Homebrew/extend/ARGV.rb
index 0e2bfac51..bc6463594 100644
--- a/Library/Homebrew/extend/ARGV.rb
+++ b/Library/Homebrew/extend/ARGV.rb
@@ -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
diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb
index 38bf16996..c87972d2e 100644
--- a/Library/Homebrew/formula_installer.rb
+++ b/Library/Homebrew/formula_installer.rb
@@ -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
diff --git a/Library/Homebrew/sandbox.rb b/Library/Homebrew/sandbox.rb
new file mode 100644
index 000000000..75d62ccf2
--- /dev/null
+++ b/Library/Homebrew/sandbox.rb
@@ -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
diff --git a/Library/Homebrew/test/test_sandbox.rb b/Library/Homebrew/test/test_sandbox.rb
new file mode 100644
index 000000000..4564edb3b
--- /dev/null
+++ b/Library/Homebrew/test/test_sandbox.rb
@@ -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