diff options
| author | Xu Cheng | 2015-04-09 17:42:54 +0800 | 
|---|---|---|
| committer | Xu Cheng | 2015-04-09 17:42:54 +0800 | 
| commit | e82c3ce71878d2615a29832e6989387587c99a56 (patch) | |
| tree | f05835d999d860e9c803e28ca97acccb39ee25b6 /Library/Homebrew | |
| parent | 16387ff98d61f79c8d437952405a08779f41fa1f (diff) | |
| download | homebrew-e82c3ce71878d2615a29832e6989387587c99a56.tar.bz2 | |
preliminary write control only sandbox
Closes #38361.
Signed-off-by: Xu Cheng <xucheng@me.com>
Diffstat (limited to 'Library/Homebrew')
| -rw-r--r-- | Library/Homebrew/extend/ARGV.rb | 4 | ||||
| -rw-r--r-- | Library/Homebrew/formula_installer.rb | 8 | ||||
| -rw-r--r-- | Library/Homebrew/sandbox.rb | 95 | ||||
| -rw-r--r-- | Library/Homebrew/test/test_sandbox.rb | 28 | 
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 | 
