| 
									
										
										
										
											2020-11-23 17:31:17 +01:00
										 |  |  | # typed: true | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  | require "erb" | 
					
						
							|  |  |  | require "tempfile" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-19 07:02:01 +02:00
										 |  |  | # Helper class for running a sub-process inside of a sandboxed environment. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # @api private | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  | class Sandbox | 
					
						
							| 
									
										
										
										
											2020-10-20 12:03:48 +02:00
										 |  |  |   extend T::Sig | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  |   SANDBOX_EXEC = "/usr/bin/sandbox-exec" | 
					
						
							| 
									
										
										
										
											2020-08-19 07:02:01 +02:00
										 |  |  |   private_constant :SANDBOX_EXEC | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-20 12:03:48 +02:00
										 |  |  |   sig { returns(T::Boolean) } | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |   def self.available? | 
					
						
							| 
									
										
										
										
											2019-01-26 17:13:14 +00:00
										 |  |  |     OS.mac? && File.executable?(SANDBOX_EXEC) | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-20 12:03:48 +02:00
										 |  |  |   sig { void } | 
					
						
							| 
									
										
										
										
											2015-04-13 18:05:15 +08:00
										 |  |  |   def initialize | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |     @profile = SandboxProfile.new | 
					
						
							| 
									
										
										
										
											2015-04-13 18:05:15 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-16 21:41:59 +08:00
										 |  |  |   def record_log(file) | 
					
						
							| 
									
										
										
										
											2015-08-28 17:30:14 +08:00
										 |  |  |     @logfile = file | 
					
						
							| 
									
										
										
										
											2015-04-16 21:41:59 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-13 18:05:15 +08:00
										 |  |  |   def add_rule(rule) | 
					
						
							|  |  |  |     @profile.add_rule(rule) | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |   def allow_write(path, options = {}) | 
					
						
							| 
									
										
										
										
											2016-09-17 15:32:44 +01:00
										 |  |  |     add_rule allow: true, operation: "file-write*", filter: path_filter(path, options[:type]) | 
					
						
							| 
									
										
										
										
											2015-04-13 18:05:15 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |   def deny_write(path, options = {}) | 
					
						
							| 
									
										
										
										
											2016-09-17 15:32:44 +01:00
										 |  |  |     add_rule allow: false, operation: "file-write*", filter: path_filter(path, options[:type]) | 
					
						
							| 
									
										
										
										
											2015-04-13 18:05:15 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def allow_write_path(path) | 
					
						
							| 
									
										
										
										
											2016-09-17 15:32:44 +01:00
										 |  |  |     allow_write path, type: :subpath | 
					
						
							| 
									
										
										
										
											2015-04-13 18:05:15 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def deny_write_path(path) | 
					
						
							| 
									
										
										
										
											2016-09-17 15:32:44 +01:00
										 |  |  |     deny_write path, type: :subpath | 
					
						
							| 
									
										
										
										
											2015-04-13 18:05:15 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def allow_write_temp_and_cache | 
					
						
							|  |  |  |     allow_write_path "/private/tmp" | 
					
						
							| 
									
										
										
										
											2015-08-25 17:34:52 +01:00
										 |  |  |     allow_write_path "/private/var/tmp" | 
					
						
							| 
									
										
										
										
											2016-09-17 15:32:44 +01:00
										 |  |  |     allow_write "^/private/var/folders/[^/]+/[^/]+/[C,T]/", type: :regex | 
					
						
							| 
									
										
										
										
											2015-04-13 18:05:15 +08:00
										 |  |  |     allow_write_path HOMEBREW_TEMP | 
					
						
							|  |  |  |     allow_write_path HOMEBREW_CACHE | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-01 23:35:29 +02:00
										 |  |  |   def allow_cvs | 
					
						
							| 
									
										
										
										
											2020-11-23 17:31:17 +01:00
										 |  |  |     allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.cvspass" | 
					
						
							| 
									
										
										
										
											2018-07-01 23:35:29 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def allow_fossil | 
					
						
							| 
									
										
										
										
											2020-11-23 17:31:17 +01:00
										 |  |  |     allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.fossil" | 
					
						
							|  |  |  |     allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.fossil-journal" | 
					
						
							| 
									
										
										
										
											2018-07-01 23:35:29 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-13 18:05:15 +08:00
										 |  |  |   def allow_write_cellar(formula) | 
					
						
							|  |  |  |     allow_write_path formula.rack | 
					
						
							|  |  |  |     allow_write_path formula.etc | 
					
						
							|  |  |  |     allow_write_path formula.var | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-25 17:34:52 +01:00
										 |  |  |   # Xcode projects expect access to certain cache/archive dirs. | 
					
						
							|  |  |  |   def allow_write_xcode | 
					
						
							| 
									
										
										
										
											2020-11-23 17:31:17 +01:00
										 |  |  |     allow_write_path "#{Dir.home(ENV.fetch("USER"))}/Library/Developer" | 
					
						
							| 
									
										
										
										
											2015-08-25 17:34:52 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-13 18:05:15 +08:00
										 |  |  |   def allow_write_log(formula) | 
					
						
							| 
									
										
										
										
											2015-04-25 22:07:06 -04:00
										 |  |  |     allow_write_path formula.logs | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-23 08:26:49 +01:00
										 |  |  |   def deny_write_homebrew_repository | 
					
						
							| 
									
										
										
										
											2015-04-23 12:33:54 +08:00
										 |  |  |     deny_write HOMEBREW_BREW_FILE | 
					
						
							| 
									
										
										
										
											2020-11-09 20:09:16 +11:00
										 |  |  |     if HOMEBREW_PREFIX.to_s == HOMEBREW_REPOSITORY.to_s | 
					
						
							| 
									
										
										
										
											2016-09-23 08:26:49 +01:00
										 |  |  |       deny_write_path HOMEBREW_LIBRARY | 
					
						
							|  |  |  |       deny_write_path HOMEBREW_REPOSITORY/".git" | 
					
						
							| 
									
										
										
										
											2020-11-09 20:09:16 +11:00
										 |  |  |     else | 
					
						
							|  |  |  |       deny_write_path HOMEBREW_REPOSITORY | 
					
						
							| 
									
										
										
										
											2016-09-23 08:26:49 +01:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2015-04-23 12:33:54 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |   def exec(*args) | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |     seatbelt = Tempfile.new(["homebrew", ".sb"], HOMEBREW_TEMP) | 
					
						
							|  |  |  |     seatbelt.write(@profile.dump) | 
					
						
							|  |  |  |     seatbelt.close | 
					
						
							|  |  |  |     @start = Time.now | 
					
						
							| 
									
										
										
										
											2020-11-23 17:31:17 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     begin | 
					
						
							|  |  |  |       T.unsafe(self).safe_system SANDBOX_EXEC, "-f", seatbelt.path, *args | 
					
						
							|  |  |  |     rescue | 
					
						
							|  |  |  |       @failed = true | 
					
						
							|  |  |  |       raise | 
					
						
							|  |  |  |     ensure | 
					
						
							|  |  |  |       seatbelt.unlink | 
					
						
							|  |  |  |       sleep 0.1 # wait for a bit to let syslog catch up the latest events. | 
					
						
							|  |  |  |       syslog_args = %W[
 | 
					
						
							|  |  |  |         -F $((Time)(local))\ $(Sender)[$(PID)]:\ $(Message) | 
					
						
							|  |  |  |         -k Time ge #{@start.to_i} | 
					
						
							|  |  |  |         -k Message S deny | 
					
						
							|  |  |  |         -k Sender kernel | 
					
						
							|  |  |  |         -o | 
					
						
							|  |  |  |         -k Time ge #{@start.to_i} | 
					
						
							|  |  |  |         -k Message S deny | 
					
						
							|  |  |  |         -k Sender sandboxd | 
					
						
							|  |  |  |       ] | 
					
						
							|  |  |  |       logs = Utils.popen_read("syslog", *syslog_args) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # These messages are confusing and non-fatal, so don't report them. | 
					
						
							|  |  |  |       logs = logs.lines.reject { |l| l.match(/^.*Python\(\d+\) deny file-write.*pyc$/) }.join | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       unless logs.empty? | 
					
						
							|  |  |  |         if @logfile | 
					
						
							|  |  |  |           File.open(@logfile, "w") do |log| | 
					
						
							|  |  |  |             log.write logs | 
					
						
							|  |  |  |             log.write "\nWe use time to filter sandbox log. Therefore, unrelated logs may be recorded.\n" | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2018-03-07 16:14:55 +00:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2015-08-28 17:30:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 17:31:17 +01:00
										 |  |  |         if @failed && Homebrew::EnvConfig.verbose? | 
					
						
							|  |  |  |           ohai "Sandbox log" | 
					
						
							|  |  |  |           puts logs | 
					
						
							|  |  |  |           $stdout.flush # without it, brew test-bot would fail to catch the log | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2015-08-28 17:30:14 +08:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def expand_realpath(path) | 
					
						
							|  |  |  |     raise unless path.absolute? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |     path.exist? ? path.realpath : expand_realpath(path.parent)/path.basename | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-13 18:05:15 +08:00
										 |  |  |   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))}\"" | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-19 07:02:01 +02:00
										 |  |  |   # Configuration profile for a sandbox. | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |   class SandboxProfile | 
					
						
							| 
									
										
										
										
											2020-10-20 12:03:48 +02:00
										 |  |  |     extend T::Sig | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  |     SEATBELT_ERB = <<~ERB | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |       (version 1) | 
					
						
							|  |  |  |       (debug deny) ; log all denied operations to /var/log/system.log | 
					
						
							|  |  |  |       <%= rules.join("\n") %> | 
					
						
							|  |  |  |       (allow file-write* | 
					
						
							| 
									
										
										
										
											2015-05-10 17:39:53 +08:00
										 |  |  |           (literal "/dev/ptmx") | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |           (literal "/dev/dtracehelper") | 
					
						
							|  |  |  |           (literal "/dev/null") | 
					
						
							| 
									
										
										
										
											2017-07-11 01:47:36 -07:00
										 |  |  |           (literal "/dev/random") | 
					
						
							| 
									
										
										
										
											2015-08-27 21:25:27 -07:00
										 |  |  |           (literal "/dev/zero") | 
					
						
							| 
									
										
										
										
											2015-05-10 17:39:53 +08:00
										 |  |  |           (regex #"^/dev/fd/[0-9]+$") | 
					
						
							| 
									
										
										
										
											2019-10-07 14:51:33 +01:00
										 |  |  |           (regex #"^/dev/tty[a-z0-9]*$") | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |           ) | 
					
						
							| 
									
										
										
										
											2020-06-06 19:12:12 +01:00
										 |  |  |       (deny file-write*) ; deny non-allowlist file write operations | 
					
						
							| 
									
										
										
										
											2015-09-15 11:46:56 +08:00
										 |  |  |       (allow process-exec | 
					
						
							|  |  |  |           (literal "/bin/ps") | 
					
						
							|  |  |  |           (with no-sandbox) | 
					
						
							|  |  |  |           ) ; allow certain processes running without sandbox | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |       (allow default) ; allow everything else | 
					
						
							| 
									
										
										
										
											2018-07-11 15:17:40 +02:00
										 |  |  |     ERB | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     attr_reader :rules | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-20 12:03:48 +02:00
										 |  |  |     sig { void } | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |     def initialize | 
					
						
							|  |  |  |       @rules = [] | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def add_rule(rule) | 
					
						
							| 
									
										
										
										
											2019-04-18 17:33:02 +09:00
										 |  |  |       s = +"(" | 
					
						
							| 
									
										
										
										
											2017-09-24 19:24:46 +01:00
										 |  |  |       s << (rule[:allow] ? "allow" : "deny") | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |       s << " #{rule[:operation]}" | 
					
						
							|  |  |  |       s << " (#{rule[:filter]})" if rule[:filter] | 
					
						
							|  |  |  |       s << " (with #{rule[:modifier]})" if rule[:modifier] | 
					
						
							|  |  |  |       s << ")" | 
					
						
							| 
									
										
										
										
											2019-04-18 17:33:02 +09:00
										 |  |  |       @rules << s.freeze | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def dump | 
					
						
							|  |  |  |       ERB.new(SEATBELT_ERB).result(binding) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2020-08-19 07:02:01 +02:00
										 |  |  |   private_constant :SandboxProfile | 
					
						
							| 
									
										
										
										
											2015-04-09 17:42:54 +08:00
										 |  |  | end |