Skip to content

Commit

Permalink
Merge pull request #17148 from alebcay/sandbox-strict-typing
Browse files Browse the repository at this point in the history
sandbox: enable strict typing
  • Loading branch information
MikeMcQuaid committed Apr 25, 2024
2 parents 34ea988 + 4eb4c7a commit a47c45d
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 23 deletions.
89 changes: 67 additions & 22 deletions Library/Homebrew/sandbox.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "erb"
Expand All @@ -20,69 +20,84 @@ def self.available?

sig { void }
def initialize
@profile = SandboxProfile.new
@profile = T.let(SandboxProfile.new, SandboxProfile)
@failed = T.let(false, T::Boolean)
end

sig { params(file: T.any(String, Pathname)).void }
def record_log(file)
@logfile = file
@logfile = T.let(file, T.nilable(T.any(String, Pathname)))
end

def add_rule(rule)
sig { params(allow: T::Boolean, operation: String, filter: T.nilable(String), modifier: T.nilable(String)).void }
def add_rule(allow:, operation:, filter: nil, modifier: nil)
rule = SandboxRule.new(allow:, operation:, filter:, modifier:)
@profile.add_rule(rule)
end

def allow_write(path, options = {})
add_rule allow: true, operation: "file-write*", filter: path_filter(path, options[:type])
add_rule allow: true, operation: "file-write-setugid", filter: path_filter(path, options[:type])
sig { params(path: T.any(String, Pathname), type: Symbol).void }
def allow_write(path:, type: :literal)
add_rule allow: true, operation: "file-write*", filter: path_filter(path, type)
add_rule allow: true, operation: "file-write-setugid", filter: path_filter(path, type)
end

def deny_write(path, options = {})
add_rule allow: false, operation: "file-write*", filter: path_filter(path, options[:type])
sig { params(path: T.any(String, Pathname), type: Symbol).void }
def deny_write(path:, type: :literal)
add_rule allow: false, operation: "file-write*", filter: path_filter(path, type)
end

sig { params(path: T.any(String, Pathname)).void }
def allow_write_path(path)
allow_write path, type: :subpath
allow_write path:, type: :subpath
end

sig { params(path: T.any(String, Pathname)).void }
def deny_write_path(path)
deny_write path, type: :subpath
deny_write path:, type: :subpath
end

sig { void }
def allow_write_temp_and_cache
allow_write_path "/private/tmp"
allow_write_path "/private/var/tmp"
allow_write "^/private/var/folders/[^/]+/[^/]+/[C,T]/", type: :regex
allow_write path: "^/private/var/folders/[^/]+/[^/]+/[C,T]/", type: :regex
allow_write_path HOMEBREW_TEMP
allow_write_path HOMEBREW_CACHE
end

sig { void }
def allow_cvs
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.cvspass"
end

sig { void }
def allow_fossil
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.fossil"
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.fossil-journal"
end

sig { params(formula: Formula).void }
def allow_write_cellar(formula)
allow_write_path formula.rack
allow_write_path formula.etc
allow_write_path formula.var
end

# Xcode projects expect access to certain cache/archive dirs.
sig { void }
def allow_write_xcode
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/Library/Developer"
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/Library/Caches/org.swift.swiftpm"
end

sig { params(formula: Formula).void }
def allow_write_log(formula)
allow_write_path formula.logs
end

sig { void }
def deny_write_homebrew_repository
deny_write HOMEBREW_BREW_FILE
deny_write path: HOMEBREW_BREW_FILE
if HOMEBREW_PREFIX.to_s == HOMEBREW_REPOSITORY.to_s
deny_write_path HOMEBREW_LIBRARY
deny_write_path HOMEBREW_REPOSITORY/".git"
Expand Down Expand Up @@ -117,11 +132,12 @@ def deny_all_network_except_pipe(path)
allow_network path:, type: :literal
end

sig { params(args: T.any(String, Pathname)).void }
def exec(*args)
seatbelt = Tempfile.new(["homebrew", ".sb"], HOMEBREW_TEMP)
seatbelt.write(@profile.dump)
seatbelt.close
@start = Time.now
@start = T.let(Time.now, T.nilable(Time))

begin
command = [SANDBOX_EXEC, "-f", seatbelt.path, *args]
Expand Down Expand Up @@ -219,19 +235,45 @@ def exec(*args)

private

sig { params(path: Pathname).returns(Pathname) }
def expand_realpath(path)
raise unless path.absolute?

path.exist? ? path.realpath : expand_realpath(path.parent)/path.basename
end

sig { params(path: T.any(String, Pathname), type: Symbol).returns(String) }
def path_filter(path, type)
case type
when :regex then "regex #\"#{path}\""
when :subpath then "subpath \"#{expand_realpath(Pathname.new(path))}\""
when :literal, nil then "literal \"#{expand_realpath(Pathname.new(path))}\""
when :regex then "regex #\"#{path}\""
when :subpath then "subpath \"#{expand_realpath(Pathname.new(path))}\""
when :literal then "literal \"#{expand_realpath(Pathname.new(path))}\""
else raise ArgumentError, "Invalid path filter type: #{type}"
end
end

class SandboxRule
sig { returns(T::Boolean) }
attr_reader :allow

sig { returns(String) }
attr_reader :operation

sig { returns(T.nilable(String)) }
attr_reader :filter

sig { returns(T.nilable(String)) }
attr_reader :modifier

sig { params(allow: T::Boolean, operation: String, filter: T.nilable(String), modifier: T.nilable(String)).void }
def initialize(allow:, operation:, filter:, modifier:)
@allow = allow
@operation = operation
@filter = filter
@modifier = modifier
end
end
private_constant :SandboxRule

# Configuration profile for a sandbox.
class SandboxProfile
Expand All @@ -256,23 +298,26 @@ class SandboxProfile
(allow default) ; allow everything else
ERB

sig { returns(T::Array[String]) }
attr_reader :rules

sig { void }
def initialize
@rules = []
@rules = T.let([], T::Array[String])
end

sig { params(rule: SandboxRule).void }
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 << (rule.allow ? "allow" : "deny")
s << " #{rule.operation}"
s << " (#{rule.filter})" if rule.filter
s << " (with #{rule.modifier})" if rule.modifier
s << ")"
@rules << s.freeze
end

sig { returns(String) }
def dump
ERB.new(SEATBELT_ERB).result(binding)
end
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/test/sandbox_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
end

specify "#allow_write" do
sandbox.allow_write file
sandbox.allow_write path: file
sandbox.exec "touch", file

expect(file).to exist
Expand Down

0 comments on commit a47c45d

Please sign in to comment.