From a16dc8e4093a64beec8cb9ac9a4460d5fd9886e8 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Sun, 14 Jul 2024 12:09:07 -0400 Subject: [PATCH 1/6] Use `/private/tmp/homebrew` as `HOMEBREW_TEMP` --- Library/Homebrew/brew.sh | 4 ++-- Library/Homebrew/env_config.rb | 2 +- Library/Homebrew/sandbox.rb | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Library/Homebrew/brew.sh b/Library/Homebrew/brew.sh index f837c599d3267..efc2c7256fff3 100644 --- a/Library/Homebrew/brew.sh +++ b/Library/Homebrew/brew.sh @@ -53,12 +53,12 @@ if [[ -n "${HOMEBREW_MACOS}" ]] then HOMEBREW_DEFAULT_CACHE="${HOME}/Library/Caches/Homebrew" HOMEBREW_DEFAULT_LOGS="${HOME}/Library/Logs/Homebrew" - HOMEBREW_DEFAULT_TEMP="/private/tmp" + HOMEBREW_DEFAULT_TEMP="/private/tmp/homebrew" else CACHE_HOME="${HOMEBREW_XDG_CACHE_HOME:-${HOME}/.cache}" HOMEBREW_DEFAULT_CACHE="${CACHE_HOME}/Homebrew" HOMEBREW_DEFAULT_LOGS="${CACHE_HOME}/Homebrew/Logs" - HOMEBREW_DEFAULT_TEMP="/tmp" + HOMEBREW_DEFAULT_TEMP="/tmp/homebrew" fi realpath() { diff --git a/Library/Homebrew/env_config.rb b/Library/Homebrew/env_config.rb index 35b56c45eec87..4a2c93799addd 100644 --- a/Library/Homebrew/env_config.rb +++ b/Library/Homebrew/env_config.rb @@ -432,7 +432,7 @@ module EnvConfig "different volumes, as macOS has trouble moving symlinks across volumes when the target " \ "does not yet exist. This issue typically occurs when using FileVault or custom SSD " \ "configurations.", - default_text: "macOS: `/private/tmp`, Linux: `/tmp`.", + default_text: "macOS: `/private/tmp/homebrew`, Linux: `/tmp/homebrew`.", default: HOMEBREW_DEFAULT_TEMP, }, HOMEBREW_UPDATE_TO_TAG: { diff --git a/Library/Homebrew/sandbox.rb b/Library/Homebrew/sandbox.rb index 5fe1f43766510..fc06bfcc717de 100644 --- a/Library/Homebrew/sandbox.rb +++ b/Library/Homebrew/sandbox.rb @@ -57,7 +57,6 @@ def deny_write_path(path) sig { void } def allow_write_temp_and_cache - allow_write_path "/private/tmp" allow_write_path "/private/var/tmp" allow_write path: "^/private/var/folders/[^/]+/[^/]+/[C,T]/", type: :regex allow_write_path HOMEBREW_TEMP From a8231e00446d942cbf31e9df429e4bdb9f9dc1d6 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Sun, 14 Jul 2024 14:36:34 -0400 Subject: [PATCH 2/6] Add `reduce_sandbox!` DSL --- Library/Homebrew/dev-cmd/test.rb | 2 +- Library/Homebrew/formula.rb | 37 +++++++++++++++++++++++++++ Library/Homebrew/formula_installer.rb | 4 +-- Library/Homebrew/sandbox.rb | 11 +++++--- 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/Library/Homebrew/dev-cmd/test.rb b/Library/Homebrew/dev-cmd/test.rb index 38684a29bedb4..8a3e3be1fa230 100644 --- a/Library/Homebrew/dev-cmd/test.rb +++ b/Library/Homebrew/dev-cmd/test.rb @@ -86,7 +86,7 @@ def run sandbox = Sandbox.new f.logs.mkpath sandbox.record_log(f.logs/"test.sandbox.log") - sandbox.allow_write_temp_and_cache + sandbox.allow_write_temp_and_cache(formula) sandbox.allow_write_log(f) sandbox.allow_write_xcode sandbox.allow_write_path(HOMEBREW_PREFIX/"var/cache") diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 11ee34f31466e..8fc0f650d6a65 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1550,6 +1550,15 @@ def link_overwrite?(path) # @see .disable! delegate disable_reason: :"self.class" + # Sandbox rules that should be skipped when installing or testing this {Formula}. + # Returns `nil` if there are no sandbox rules to skip. + # @!method reduced_sandbox + # @return [Array] + # @see .reduce_sandbox + def reduced_sandbox + self.class.reduced_sandbox || [] + end + sig { returns(T::Boolean) } def skip_cxxstdlib_check? false @@ -3276,6 +3285,9 @@ def freeze # The reason for why this software is not linked (by default) to {::HOMEBREW_PREFIX}. attr_reader :keg_only_reason + # The types of sandbox restrictions that should be lifted from the formula. + attr_reader :reduced_sandbox + # A one-line description of the software. Used by users to get an overview # of the software and Homebrew maintainers. # Shows when running `brew info`. @@ -4295,6 +4307,31 @@ def link_overwrite(*paths) paths.flatten! link_overwrite_paths.merge(paths) end + + # Skip certain sandbox restrictions when installing this formula. + # This can be useful if the upstream build system needs to write to + # locations that are protected by sandbox restrictions. + # + # ### Example + # + # If upstream needs to write to `/private/tmp`: + # + # ```ruby + # reduce_sandbox :allow_write_to_temp + # ``` + def reduce_sandbox!(*types) + invalid_types = types.select { |type| Sandbox::SANDBOX_REDUCTIONS.exclude?(type) } + if invalid_types.any? + noun = if invalid_types.count > 1 + "types" + else + "type" + end + raise ArgumentError, "Unsupported sandbox reduction #{noun}: #{invalid_types.join(", ")}" + end + + @reduced_sandbox = types + end end end diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 8e6b56049516b..00c5f46775113 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -937,7 +937,7 @@ def build formula.logs.mkpath sandbox.record_log(formula.logs/"build.sandbox.log") sandbox.allow_write_path(Dir.home) if interactive? - sandbox.allow_write_temp_and_cache + sandbox.allow_write_temp_and_cache(formula) sandbox.allow_write_log(formula) sandbox.allow_cvs sandbox.allow_fossil @@ -1153,7 +1153,7 @@ def post_install sandbox = Sandbox.new formula.logs.mkpath sandbox.record_log(formula.logs/"postinstall.sandbox.log") - sandbox.allow_write_temp_and_cache + sandbox.allow_write_temp_and_cache(formula) sandbox.allow_write_log(formula) sandbox.allow_write_xcode sandbox.deny_write_homebrew_repository diff --git a/Library/Homebrew/sandbox.rb b/Library/Homebrew/sandbox.rb index fc06bfcc717de..83dabe5c93aac 100644 --- a/Library/Homebrew/sandbox.rb +++ b/Library/Homebrew/sandbox.rb @@ -11,6 +11,8 @@ class Sandbox SANDBOX_EXEC = "/usr/bin/sandbox-exec" private_constant :SANDBOX_EXEC + SANDBOX_REDUCTIONS = [:allow_write_to_temp].freeze + sig { returns(T::Boolean) } def self.available? false @@ -55,9 +57,12 @@ def deny_write_path(path) deny_write path:, type: :subpath end - sig { void } - def allow_write_temp_and_cache - allow_write_path "/private/var/tmp" + sig { params(formula: T.nilable(Formula)).void } + def allow_write_temp_and_cache(formula = nil) + if formula&.reduced_sandbox&.include?(:allow_write_to_temp) + allow_write_path "/private/tmp" + allow_write_path "/private/var/tmp" + end allow_write path: "^/private/var/folders/[^/]+/[^/]+/[C,T]/", type: :regex allow_write_path HOMEBREW_TEMP allow_write_path HOMEBREW_CACHE From dfcaad018a0570d2f304aae1a108e55b405fc8f4 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Sun, 14 Jul 2024 14:40:14 -0400 Subject: [PATCH 3/6] Fix typo --- Library/Homebrew/dev-cmd/test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/dev-cmd/test.rb b/Library/Homebrew/dev-cmd/test.rb index 8a3e3be1fa230..bcab2320235c1 100644 --- a/Library/Homebrew/dev-cmd/test.rb +++ b/Library/Homebrew/dev-cmd/test.rb @@ -86,7 +86,7 @@ def run sandbox = Sandbox.new f.logs.mkpath sandbox.record_log(f.logs/"test.sandbox.log") - sandbox.allow_write_temp_and_cache(formula) + sandbox.allow_write_temp_and_cache(f) sandbox.allow_write_log(f) sandbox.allow_write_xcode sandbox.allow_write_path(HOMEBREW_PREFIX/"var/cache") From 300fcf1afa0a2d29c200fa5c30249422c11ad600 Mon Sep 17 00:00:00 2001 From: Thierry Moisan Date: Mon, 15 Jul 2024 15:02:35 -0400 Subject: [PATCH 4/6] Add deny_signal method --- Library/Homebrew/formula.rb | 1 + Library/Homebrew/formula_installer.rb | 1 + Library/Homebrew/sandbox.rb | 10 +++++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 8fc0f650d6a65..6298a2da05b3f 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -35,6 +35,7 @@ require "extend/on_system" require "api" require "extend/api_hashable" +require "sandbox" # A formula provides instructions and metadata for Homebrew to install a piece # of software. Every Homebrew formula is a {Formula}. diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 00c5f46775113..29b9d4da152de 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -943,6 +943,7 @@ def build sandbox.allow_fossil sandbox.allow_write_xcode sandbox.allow_write_cellar(formula) + sandbox.deny_signal(formula) sandbox.deny_all_network_except_pipe(error_pipe) unless formula.network_access_allowed?(:build) sandbox.exec(*args) else diff --git a/Library/Homebrew/sandbox.rb b/Library/Homebrew/sandbox.rb index 83dabe5c93aac..ba923b9cc9a66 100644 --- a/Library/Homebrew/sandbox.rb +++ b/Library/Homebrew/sandbox.rb @@ -11,7 +11,7 @@ class Sandbox SANDBOX_EXEC = "/usr/bin/sandbox-exec" private_constant :SANDBOX_EXEC - SANDBOX_REDUCTIONS = [:allow_write_to_temp].freeze + SANDBOX_REDUCTIONS = [:allow_write_to_temp, :allow_signal].freeze sig { returns(T::Boolean) } def self.available? @@ -68,6 +68,14 @@ def allow_write_temp_and_cache(formula = nil) allow_write_path HOMEBREW_CACHE end + sig { params(formula: T.nilable(Formula)).void } + def deny_signal(formula = nil) + puts "deny_signal: #{formula&.reduced_sandbox&.include?(:allow_signal)}" + unless formula&.reduced_sandbox&.include?(:allow_signal) + add_rule allow: false, operation: "signal", filter: "target others" + end + end + sig { void } def allow_cvs allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.cvspass" From c7ba27b33b840ec27ab48d6f1b3e07f6a7d8cfef Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Mon, 15 Jul 2024 15:48:08 -0400 Subject: [PATCH 5/6] Update DSL to make it more flexible --- Library/Homebrew/ast_constants.rb | 1 + Library/Homebrew/dev-cmd/test.rb | 4 +- Library/Homebrew/formula.rb | 66 ++++++++++++++++++++------- Library/Homebrew/formula_installer.rb | 9 ++-- Library/Homebrew/sandbox.rb | 26 +++++------ 5 files changed, 73 insertions(+), 33 deletions(-) diff --git a/Library/Homebrew/ast_constants.rb b/Library/Homebrew/ast_constants.rb index 38df04869e65d..1b28d06bf9e9f 100644 --- a/Library/Homebrew/ast_constants.rb +++ b/Library/Homebrew/ast_constants.rb @@ -46,6 +46,7 @@ [{ name: :needs, type: :method_call }], [{ name: :allow_network_access!, type: :method_call }], [{ name: :deny_network_access!, type: :method_call }], + [{ name: :allow_in_sandbox!, type: :method_call }], [{ name: :install, type: :method_definition }], [{ name: :post_install, type: :method_definition }], [{ name: :caveats, type: :method_definition }], diff --git a/Library/Homebrew/dev-cmd/test.rb b/Library/Homebrew/dev-cmd/test.rb index bcab2320235c1..2c17d2360d714 100644 --- a/Library/Homebrew/dev-cmd/test.rb +++ b/Library/Homebrew/dev-cmd/test.rb @@ -86,7 +86,7 @@ def run sandbox = Sandbox.new f.logs.mkpath sandbox.record_log(f.logs/"test.sandbox.log") - sandbox.allow_write_temp_and_cache(f) + sandbox.allow_write_temp_and_cache sandbox.allow_write_log(f) sandbox.allow_write_xcode sandbox.allow_write_path(HOMEBREW_PREFIX/"var/cache") @@ -94,6 +94,8 @@ def run sandbox.allow_write_path(HOMEBREW_PREFIX/"var/log") sandbox.allow_write_path(HOMEBREW_PREFIX/"var/run") sandbox.deny_all_network_except_pipe(error_pipe) unless f.class.network_access_allowed?(:test) + sandbox.allow_write_global_temp if f.allowed_in_sandbox?(:write_to_temp, phase: :test) + sandbox.deny_signal if f.allowed_in_sandbox?(:signal, phase: :test) sandbox.exec(*exec_args) else exec(*exec_args) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 6298a2da05b3f..e9c107ec5b3a7 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1551,13 +1551,17 @@ def link_overwrite?(path) # @see .disable! delegate disable_reason: :"self.class" - # Sandbox rules that should be skipped when installing or testing this {Formula}. - # Returns `nil` if there are no sandbox rules to skip. - # @!method reduced_sandbox - # @return [Array] - # @see .reduce_sandbox - def reduced_sandbox - self.class.reduced_sandbox || [] + # Whether or not the given sandbox rule should be skipped during the given phase in this {Formula}. + # @!method allowed_in_sandbox? + # @param type [Symbol] the type of sandbox rule + # @param phase [Symbol] the phase to check + # @return [Boolean] + # @see .allow_in_sandbox + def allowed_in_sandbox?(type, phase:) + return false unless self.class.allowed_in_sandbox + return false unless self.class.allowed_in_sandbox.key?(phase) + + self.class.allowed_in_sandbox[phase].include?(type) end sig { returns(T::Boolean) } @@ -3287,7 +3291,7 @@ def freeze attr_reader :keg_only_reason # The types of sandbox restrictions that should be lifted from the formula. - attr_reader :reduced_sandbox + attr_reader :allowed_in_sandbox # A one-line description of the software. Used by users to get an overview # of the software and Homebrew maintainers. @@ -4309,29 +4313,59 @@ def link_overwrite(*paths) link_overwrite_paths.merge(paths) end - # Skip certain sandbox restrictions when installing this formula. + # Skip certain sandbox restrictions when installing and testing this formula. # This can be useful if the upstream build system needs to write to - # locations that are protected by sandbox restrictions. + # locations that are protected by sandbox restrictions. Passing a + # phase is optional, and if not provided, the rule will be applied to + # all phases. The possible phases are `:build`, `:postinstall`, and `:test`. # # ### Example # - # If upstream needs to write to `/private/tmp`: + # If the formula needs to write to `/private/tmp` in all phases: # # ```ruby - # reduce_sandbox :allow_write_to_temp + # allow_in_sandbox! :write_to_temp # ``` - def reduce_sandbox!(*types) - invalid_types = types.select { |type| Sandbox::SANDBOX_REDUCTIONS.exclude?(type) } + # + # If the formula needs to send signals in the `:test` phase: + # ```ruby + # allow_in_sandbox! :signal, phase: :test + # ``` + # + # If the formula needs to write to `/private/tmp` and send signals + # in the `:test` and `:install` phase: + # ```ruby + # allow_in_sandbox! :write_to_temp, :signal, phase: [:test, :install] + # ``` + def allow_in_sandbox!(*types, phase: nil) + invalid_types = types.select { |type| Sandbox::SANDBOX_DSL_RULES.exclude?(type) } if invalid_types.any? noun = if invalid_types.count > 1 "types" else "type" end - raise ArgumentError, "Unsupported sandbox reduction #{noun}: #{invalid_types.join(", ")}" + raise ArgumentError, "Unsupported allow in sandbox item #{noun}: #{invalid_types.join(", ")}" + end + + phase ||= Sandbox::SANDBOX_DSL_PHASES + phases = Array(phase) + invalid_phases = phases.select { |p| Sandbox::SANDBOX_DSL_PHASES.exclude?(p) } + if invalid_phases.any? + noun = if invalid_phases.count > 1 + "phases" + else + "phase" + end + raise ArgumentError, "Unsupported sandbox phase #{noun}: #{invalid_phases.join(", ")}" end - @reduced_sandbox = types + @allowed_in_sandbox ||= {} + phases.each do |p| + @allowed_in_sandbox[p] ||= [] + @allowed_in_sandbox[p].concat(types) + @allowed_in_sandbox[p] = @allowed_in_sandbox[p].uniq + end end end end diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 29b9d4da152de..5d81cfc56e54d 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -937,14 +937,15 @@ def build formula.logs.mkpath sandbox.record_log(formula.logs/"build.sandbox.log") sandbox.allow_write_path(Dir.home) if interactive? - sandbox.allow_write_temp_and_cache(formula) + sandbox.allow_write_temp_and_cache sandbox.allow_write_log(formula) sandbox.allow_cvs sandbox.allow_fossil sandbox.allow_write_xcode sandbox.allow_write_cellar(formula) - sandbox.deny_signal(formula) sandbox.deny_all_network_except_pipe(error_pipe) unless formula.network_access_allowed?(:build) + sandbox.allow_write_global_temp if formula.allowed_in_sandbox?(:write_to_temp, phase: :build) + sandbox.deny_signal if formula.allowed_in_sandbox?(:signal, phase: :build) sandbox.exec(*args) else exec(*args) @@ -1154,7 +1155,7 @@ def post_install sandbox = Sandbox.new formula.logs.mkpath sandbox.record_log(formula.logs/"postinstall.sandbox.log") - sandbox.allow_write_temp_and_cache(formula) + sandbox.allow_write_temp_and_cache sandbox.allow_write_log(formula) sandbox.allow_write_xcode sandbox.deny_write_homebrew_repository @@ -1163,6 +1164,8 @@ def post_install Keg::KEG_LINK_DIRECTORIES.each do |dir| sandbox.allow_write_path "#{HOMEBREW_PREFIX}/#{dir}" end + sandbox.allow_write_global_temp if formula.allowed_in_sandbox?(:write_to_temp, phase: :postinstall) + sandbox.deny_signal if formula.allowed_in_sandbox?(:signal, phase: :postinstall) sandbox.exec(*args) else exec(*args) diff --git a/Library/Homebrew/sandbox.rb b/Library/Homebrew/sandbox.rb index ba923b9cc9a66..b65c2ebbaec27 100644 --- a/Library/Homebrew/sandbox.rb +++ b/Library/Homebrew/sandbox.rb @@ -11,7 +11,8 @@ class Sandbox SANDBOX_EXEC = "/usr/bin/sandbox-exec" private_constant :SANDBOX_EXEC - SANDBOX_REDUCTIONS = [:allow_write_to_temp, :allow_signal].freeze + SANDBOX_DSL_RULES = [:write_to_temp, :signal].freeze + SANDBOX_DSL_PHASES = [:build, :postinstall, :test].freeze sig { returns(T::Boolean) } def self.available? @@ -57,23 +58,22 @@ def deny_write_path(path) deny_write path:, type: :subpath end - sig { params(formula: T.nilable(Formula)).void } - def allow_write_temp_and_cache(formula = nil) - if formula&.reduced_sandbox&.include?(:allow_write_to_temp) - allow_write_path "/private/tmp" - allow_write_path "/private/var/tmp" - end + sig { void } + def allow_write_temp_and_cache allow_write path: "^/private/var/folders/[^/]+/[^/]+/[C,T]/", type: :regex allow_write_path HOMEBREW_TEMP allow_write_path HOMEBREW_CACHE end - sig { params(formula: T.nilable(Formula)).void } - def deny_signal(formula = nil) - puts "deny_signal: #{formula&.reduced_sandbox&.include?(:allow_signal)}" - unless formula&.reduced_sandbox&.include?(:allow_signal) - add_rule allow: false, operation: "signal", filter: "target others" - end + sig { void } + def allow_write_global_temp + allow_write_path "/private/tmp" + allow_write_path "/private/var/tmp" + end + + sig { void } + def deny_signal + add_rule allow: false, operation: "signal", filter: "target others" end sig { void } From a3a49801a0809cb84a3f6ae51f827618ecf57cac Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Mon, 15 Jul 2024 16:13:32 -0400 Subject: [PATCH 6/6] Incorporate `{allow,deny}_network_access!` into `allow_in_sandbox!` --- Library/Homebrew/dev-cmd/test.rb | 4 +- Library/Homebrew/formula.rb | 57 ++++++++------------------- Library/Homebrew/formula_installer.rb | 10 +++-- Library/Homebrew/sandbox.rb | 2 +- 4 files changed, 26 insertions(+), 47 deletions(-) diff --git a/Library/Homebrew/dev-cmd/test.rb b/Library/Homebrew/dev-cmd/test.rb index 2c17d2360d714..4cf2aaa4bc917 100644 --- a/Library/Homebrew/dev-cmd/test.rb +++ b/Library/Homebrew/dev-cmd/test.rb @@ -93,9 +93,9 @@ def run sandbox.allow_write_path(HOMEBREW_PREFIX/"var/homebrew/locks") sandbox.allow_write_path(HOMEBREW_PREFIX/"var/log") sandbox.allow_write_path(HOMEBREW_PREFIX/"var/run") - sandbox.deny_all_network_except_pipe(error_pipe) unless f.class.network_access_allowed?(:test) + sandbox.deny_all_network_except_pipe(error_pipe) unless f.allowed_in_sandbox?(:network, phase: :test) sandbox.allow_write_global_temp if f.allowed_in_sandbox?(:write_to_temp, phase: :test) - sandbox.deny_signal if f.allowed_in_sandbox?(:signal, phase: :test) + sandbox.deny_signal unless f.allowed_in_sandbox?(:signal, phase: :test) sandbox.exec(*exec_args) else exec(*exec_args) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index e9c107ec5b3a7..efc92e31029a0 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -78,11 +78,6 @@ class Formula extend Attrable extend APIHashable - SUPPORTED_NETWORK_ACCESS_PHASES = [:build, :test, :postinstall].freeze - private_constant :SUPPORTED_NETWORK_ACCESS_PHASES - DEFAULT_NETWORK_ACCESS_ALLOWED = true - private_constant :DEFAULT_NETWORK_ACCESS_ALLOWED - # The name of this {Formula}. # e.g. `this-formula` # @@ -1557,11 +1552,18 @@ def link_overwrite?(path) # @param phase [Symbol] the phase to check # @return [Boolean] # @see .allow_in_sandbox + sig { params(type: Symbol, phase: Symbol).returns(T::Boolean) } def allowed_in_sandbox?(type, phase:) + raise ArgumentError, "Unknown phase: #{phase}" unless Sandbox::SANDBOX_DSL_PHASES.include?(phase) + return false unless self.class.allowed_in_sandbox return false unless self.class.allowed_in_sandbox.key?(phase) - self.class.allowed_in_sandbox[phase].include?(type) + allowed = self.class.allowed_in_sandbox[phase].include?(type) + return allowed if type != :network + + env_var = Homebrew::EnvConfig.send(:"formula_#{phase}_network") + env_var.nil? ? allowed : env_var == "allow" end sig { returns(T::Boolean) } @@ -3254,9 +3256,7 @@ def inherited(child) @skip_clean_paths = Set.new @link_overwrite_paths = Set.new @loaded_from_api = false - @network_access_allowed = SUPPORTED_NETWORK_ACCESS_PHASES.to_h do |phase| - [phase, DEFAULT_NETWORK_ACCESS_ALLOWED] - end + @allowed_in_sandbox = {} end end @@ -3387,16 +3387,8 @@ def license(args = nil) # @!attribute [w] allow_network_access! sig { params(phases: T.any(Symbol, T::Array[Symbol])).void } def allow_network_access!(phases = []) - phases_array = Array(phases) - if phases_array.empty? - @network_access_allowed.each_key { |phase| @network_access_allowed[phase] = true } - else - phases_array.each do |phase| - raise ArgumentError, "Unknown phase: #{phase}" unless SUPPORTED_NETWORK_ACCESS_PHASES.include?(phase) - - @network_access_allowed[phase] = true - end - end + # TODO: uncomment for Homebrew 3.4.0 + # odeprecated "`allow_network_access!`", "`allow_in_sandbox! :network`" end # The phases for which network access is denied. By default, network @@ -3419,27 +3411,11 @@ def allow_network_access!(phases = []) # ``` # # @!attribute [w] deny_network_access! - sig { params(phases: T.any(Symbol, T::Array[Symbol])).void } - def deny_network_access!(phases = []) - phases_array = Array(phases) - if phases_array.empty? - @network_access_allowed.each_key { |phase| @network_access_allowed[phase] = false } - else - phases_array.each do |phase| - raise ArgumentError, "Unknown phase: #{phase}" unless SUPPORTED_NETWORK_ACCESS_PHASES.include?(phase) - - @network_access_allowed[phase] = false - end - end - end - - # Whether the specified phase should be forced offline. - sig { params(phase: Symbol).returns(T::Boolean) } - def network_access_allowed?(phase) - raise ArgumentError, "Unknown phase: #{phase}" unless SUPPORTED_NETWORK_ACCESS_PHASES.include?(phase) - - env_var = Homebrew::EnvConfig.send(:"formula_#{phase}_network") - env_var.nil? ? @network_access_allowed[phase] : env_var == "allow" + sig { params(phases: T.nilable(T.any(Symbol, T::Array[Symbol]))).void } + def deny_network_access!(phases = nil) + # TODO: uncomment for Homebrew 3.4.0 + # odeprecated "`deny_network_access!`" + allow_in_sandbox! :network, phase: phases end # The homepage for the software. Used by users to get more information @@ -4337,6 +4313,7 @@ def link_overwrite(*paths) # ```ruby # allow_in_sandbox! :write_to_temp, :signal, phase: [:test, :install] # ``` + sig { params(types: Symbol, phase: T.nilable(T.any(Symbol, T::Array[Symbol]))).void } def allow_in_sandbox!(*types, phase: nil) invalid_types = types.select { |type| Sandbox::SANDBOX_DSL_RULES.exclude?(type) } if invalid_types.any? diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 5d81cfc56e54d..7b9355f328ba4 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -943,9 +943,9 @@ def build sandbox.allow_fossil sandbox.allow_write_xcode sandbox.allow_write_cellar(formula) - sandbox.deny_all_network_except_pipe(error_pipe) unless formula.network_access_allowed?(:build) + sandbox.deny_all_network_except_pipe(error_pipe) unless formula.allowed_in_sandbox?(:network, phase: :build) sandbox.allow_write_global_temp if formula.allowed_in_sandbox?(:write_to_temp, phase: :build) - sandbox.deny_signal if formula.allowed_in_sandbox?(:signal, phase: :build) + sandbox.deny_signal unless formula.allowed_in_sandbox?(:signal, phase: :build) sandbox.exec(*args) else exec(*args) @@ -1160,12 +1160,14 @@ def post_install sandbox.allow_write_xcode sandbox.deny_write_homebrew_repository sandbox.allow_write_cellar(formula) - sandbox.deny_all_network_except_pipe(error_pipe) unless formula.network_access_allowed?(:postinstall) Keg::KEG_LINK_DIRECTORIES.each do |dir| sandbox.allow_write_path "#{HOMEBREW_PREFIX}/#{dir}" end + unless formula.allowed_in_sandbox?(:network, phase: :postinstall) + sandbox.deny_all_network_except_pipe(error_pipe) + end sandbox.allow_write_global_temp if formula.allowed_in_sandbox?(:write_to_temp, phase: :postinstall) - sandbox.deny_signal if formula.allowed_in_sandbox?(:signal, phase: :postinstall) + sandbox.deny_signal unless formula.allowed_in_sandbox?(:signal, phase: :postinstall) sandbox.exec(*args) else exec(*args) diff --git a/Library/Homebrew/sandbox.rb b/Library/Homebrew/sandbox.rb index b65c2ebbaec27..0154646d5c168 100644 --- a/Library/Homebrew/sandbox.rb +++ b/Library/Homebrew/sandbox.rb @@ -11,7 +11,7 @@ class Sandbox SANDBOX_EXEC = "/usr/bin/sandbox-exec" private_constant :SANDBOX_EXEC - SANDBOX_DSL_RULES = [:write_to_temp, :signal].freeze + SANDBOX_DSL_RULES = [:write_to_temp, :signal, :network].freeze SANDBOX_DSL_PHASES = [:build, :postinstall, :test].freeze sig { returns(T::Boolean) }