Class: Sandbox Private

Inherits:
Object show all
Extended by:
Utils::Output::Mixin
Includes:
OS::Linux::Sandbox, OS::Mac::Sandbox, Utils::Output::Mixin
Defined in:
sandbox.rb,
extend/os/linux/sandbox/backend.rb,
extend/os/linux/sandbox/landlock.rb

Overview

This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.

Helper class for running a sub-process inside of a sandboxed environment.

Defined Under Namespace

Classes: Landlock, LinuxBackend

Constant Summary collapse

PRIVILEGED_GROUPS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

Privileged groups that are expected to be able to use a working sandbox.

%w[admin staff root wheel].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils::Output::Mixin

issue_reporting_message, odebug, odeprecated, odie, odisabled, ofail, oh1, oh1_title, ohai, ohai_title, onoe, opoo, opoo_outside_github_actions, opoo_without_github_actions_annotation, pretty_deprecated, pretty_disabled, pretty_duration, pretty_install_status, pretty_installed, pretty_uninstalled, pretty_unmarked, pretty_upgradable, pretty_warning

Constructor Details

#initializevoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



274
275
276
277
278
279
# File 'sandbox.rb', line 274

def initialize
  @profile = T.let(SandboxProfile.new, SandboxProfile)
  @failed = T.let(false, T::Boolean)
  @logfile = T.let(nil, T.nilable(T.any(String, Pathname)))
  @start = T.let(nil, T.nilable(Time))
end

Instance Attribute Details

#profileSandboxProfile (readonly)

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (SandboxProfile)


657
658
659
# File 'sandbox.rb', line 657

def profile
  @profile
end

Class Method Details

.available?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


79
80
81
# File 'sandbox.rb', line 79

def self.available?
  false
end

.avoid_nested_sandboxing?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Skip Homebrew's own sandbox when it is opted into via $HOMEBREW_AVOID_NESTED_SANDBOXING and already running inside another sandbox. The skip is only supported for an unprivileged user in a custom prefix; error out explaining why rather than silently sandboxing (and hanging) when either is not the case.

Returns:

  • (Boolean)


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'sandbox.rb', line 98

def self.avoid_nested_sandboxing?
  return false unless Homebrew::EnvConfig.avoid_nested_sandboxing?
  return false unless nested_sandbox?

  if Homebrew.default_prefix?
    odie "Refusing to skip the sandbox: `$HOMEBREW_AVOID_NESTED_SANDBOXING` is set " \
         "inside another sandbox but Homebrew is using its default prefix " \
         "(#{HOMEBREW_PREFIX}); this is only supported in a custom prefix."
  end

  privileged_group = PRIVILEGED_GROUPS.find do |name|
    group = Etc.getgrnam(name)
    group && Process.groups.include?(group.gid)
  rescue ArgumentError
    false
  end
  if privileged_group
    odie "Refusing to skip the sandbox: `$HOMEBREW_AVOID_NESTED_SANDBOXING` is set " \
         "inside another sandbox but you are in the privileged `#{privileged_group}` " \
         "group; this is only supported for an unprivileged user."
  end

  true
end

.ensure_sandbox_available!void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



187
188
189
190
191
# File 'sandbox.rb', line 187

def self.ensure_sandbox_available!
  return if available?

  raise failure_reason || "The sandbox is not available."
end

.executablePathname?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'sandbox.rb', line 241

def self.executable
  executable_candidate_paths.each do |path|
    begin
      candidate = Pathname.new(File.expand_path(executable_name, path))
    rescue ArgumentError
      next
    end

    next if !candidate.file? || !candidate.executable?
    next unless executable_usable?(candidate)

    return candidate
  end

  nil
end

.executable!Pathname

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



259
260
261
# File 'sandbox.rb', line 259

def self.executable!
  executable || raise("#{executable_name} is required to use the sandbox.")
end

.executable_candidate_paths::PATH

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



233
234
235
236
237
238
# File 'sandbox.rb', line 233

def self.executable_candidate_paths
  executable_path = Pathname.new(executable_name)
  return PATH.new(executable_path.dirname) if executable_path.absolute?

  PATH.new(ORIGINAL_PATHS, ENV.fetch("PATH"), HOMEBREW_ORIGINAL_BREW_FILE.dirname)
end

.executable_nameString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

Raises:

  • (NotImplementedError)


228
229
230
# File 'sandbox.rb', line 228

def self.executable_name
  raise NotImplementedError, "Sandbox is not implemented for this OS."
end

.executable_usable?(_candidate) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (Boolean)


264
265
266
# File 'sandbox.rb', line 264

def self.executable_usable?(_candidate)
  true
end

.failure_reasonString?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



199
200
201
202
203
# File 'sandbox.rb', line 199

def self.failure_reason
  return if state == :available

  "The sandbox is not available."
end

.full_write_isolation?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


84
# File 'sandbox.rb', line 84

def self.full_write_isolation? = true

.nested_sandbox?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Whether Homebrew is itself running inside another sandbox, which would make its own nested sandbox hang (macOS) or fail to start (Linux). Overridden per-OS.

Returns:

  • (Boolean)


90
# File 'sandbox.rb', line 90

def self.nested_sandbox? = false

.reset_state!void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



206
# File 'sandbox.rb', line 206

def self.reset_state!; end

.run_command(*command, writable_path:, deny_network: false) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'sandbox.rb', line 209

def self.run_command(*command, writable_path:, deny_network: false)
  ensure_sandbox_available!

  writable_path = Pathname(writable_path).expand_path
  if !writable_path.directory? || !writable_path.writable?
    raise UsageError,
          "`#{writable_path}` is not a writable directory."
  end

  writable_path = writable_path.realpath
  sandbox = new
  sandbox.allow_write_temp_and_cache
  sandbox.allow_write_path writable_path
  sandbox.deny_read_home
  sandbox.deny_all_network if deny_network
  sandbox.run "/bin/sh", "-c", "cd \"$1\" && shift && exec \"$@\"", "brew-sandbox-exec", writable_path, *command
end

.run_or_fork(*args, step:, warn_without_sandbox: true, &_block) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



146
147
148
149
150
151
152
153
154
# File 'sandbox.rb', line 146

def self.run_or_fork(*args, step:, warn_without_sandbox: true, &_block)
  if use_for?(step, warn_without_sandbox:)
    sandbox = new
    yield sandbox
    sandbox.run(*args)
  else
    Utils.safe_fork { exec(*args) }
  end
end

.stateSymbol

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



194
195
196
# File 'sandbox.rb', line 194

def self.state
  available? ? :available : :unavailable
end

.terminal_ioctl_requestInteger

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

Raises:

  • (NotImplementedError)


269
270
271
# File 'sandbox.rb', line 269

def self.terminal_ioctl_request
  raise NotImplementedError, "Sandbox is not implemented for this OS."
end

.use_for?(step, warn_without_sandbox: true) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • step (String)
  • warn_without_sandbox (Boolean) (defaults to: true)

Returns:

  • (Boolean)


124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'sandbox.rb', line 124

def self.use_for?(step, warn_without_sandbox: true)
  unless available?
    opoo "Sandbox unavailable: #{step} without sandboxing!" if warn_without_sandbox
    return false
  end

  if avoid_nested_sandboxing?
    opoo "#{step.capitalize} without Homebrew's sandbox; relying on the outer sandbox." if warn_without_sandbox
    return false
  end

  true
end

.with_preserved_brew_file(&block) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Landlock cannot protect bin/brew while allowing writes to bin, so a sandboxed install hook could replace brew to persist into later commands.

Parameters:

  • block (T.proc.void)


159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'sandbox.rb', line 159

def self.with_preserved_brew_file(&block)
  return yield if full_write_isolation?

  brew_file = HOMEBREW_PREFIX/"bin/brew"
  File.open(brew_file.dirname) do |brew_directory|
    brew_directory_mode = brew_directory.stat.mode & 07777
    symlink = brew_file.symlink?
    contents = symlink ? brew_file.readlink.to_s : brew_file.binread
    brew_file_mode = brew_file.lstat.mode & 07777

    begin
      yield
    ensure
      brew_directory.chmod brew_directory_mode
      if symlink && (!brew_file.symlink? || brew_file.readlink.to_s != contents)
        FileUtils.rm_rf brew_file
        brew_file.make_symlink contents
      elsif !symlink && (brew_file.symlink? || !brew_file.file? || brew_file.binread != contents ||
                         (brew_file.lstat.mode & 07777) != brew_file_mode)
        FileUtils.rm_rf brew_file
        brew_file.atomic_write contents
        brew_file.chmod brew_file_mode
      end
    end
  end
end

Instance Method Details

#add_install_hook_rules(network_access_allowed:) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:

  • network_access_allowed (Boolean)


488
489
490
491
492
493
# File 'sandbox.rb', line 488

def add_install_hook_rules(network_access_allowed:)
  allow_write_temp_and_cache
  deny_write_homebrew_repository
  deny_read_home
  deny_all_network unless network_access_allowed
end

#add_rule(allow:, operation:, filter: nil, modifier: nil) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:

  • allow (Boolean)
  • operation (String)
  • filter (SandboxPathFilter, nil) (defaults to: nil)
  • modifier (String, nil) (defaults to: nil)


290
291
292
293
# File 'sandbox.rb', line 290

def add_rule(allow:, operation:, filter: nil, modifier: nil)
  rule = SandboxRule.new(allow:, operation:, filter:, modifier:)
  @profile.add_rule(rule)
end

#allow_cvsvoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



496
497
498
# File 'sandbox.rb', line 496

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

#allow_fossilvoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



501
502
503
504
# File 'sandbox.rb', line 501

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

#allow_network(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



533
534
535
# File 'sandbox.rb', line 533

def allow_network(path:, type: :literal)
  add_rule allow: true, operation: "network*", filter: path_filter(path, type)
end

#allow_process_exec(path, no_sandbox: false) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



301
302
303
304
# File 'sandbox.rb', line 301

def allow_process_exec(path, no_sandbox: false)
  modifier = "no-sandbox" if no_sandbox
  add_rule allow: true, operation: "process-exec", filter: path_filter(path, :literal), modifier:
end

#allow_read(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



296
297
298
# File 'sandbox.rb', line 296

def allow_read(path:, type: :literal)
  add_rule allow: true, operation: "file-read*", filter: path_filter(path, type)
end

#allow_read_if_exists(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



444
445
446
447
448
449
# File 'sandbox.rb', line 444

def allow_read_if_exists(path:, type: :literal)
  return unless path
  return unless File.exist?(path)

  allow_read path:, type:
end

#allow_write(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



452
453
454
455
456
# File 'sandbox.rb', line 452

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)
  add_rule allow: true, operation: "file-write-mode", filter: path_filter(path, type)
end

#allow_write_cellar(formula) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



507
508
509
510
511
# File 'sandbox.rb', line 507

def allow_write_cellar(formula)
  allow_write_path formula.rack
  allow_write_path formula.etc
  allow_write_path formula.var
end

#allow_write_log(formula) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



517
518
519
# File 'sandbox.rb', line 517

def allow_write_log(formula)
  allow_write_path formula.logs
end

#allow_write_path(path) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



464
465
466
# File 'sandbox.rb', line 464

def allow_write_path(path)
  allow_write path:, type: :subpath
end

#allow_write_path_if_exists(path) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



469
470
471
472
473
474
# File 'sandbox.rb', line 469

def allow_write_path_if_exists(path)
  return unless path
  return unless File.exist?(path)

  allow_write_path path
end

#allow_write_temp_and_cachevoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



482
483
484
485
# File 'sandbox.rb', line 482

def allow_write_temp_and_cache
  allow_write_path HOMEBREW_TEMP
  allow_write_path HOMEBREW_CACHE
end

#allow_write_xcodevoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



514
# File 'sandbox.rb', line 514

def allow_write_xcode; end

#copy_pty_output(controller) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:

  • controller (IO)


660
661
662
663
664
665
# File 'sandbox.rb', line 660

def copy_pty_output(controller)
  controller.each_char { |c| print(c) }
rescue Errno::EIO
  # Linux marks a PTY as an I/O error when its peer closes, so treat this as EOF:
  # https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c
end

#deny_all_networkvoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



538
539
540
# File 'sandbox.rb', line 538

def deny_all_network
  add_rule allow: false, operation: "network*"
end

#deny_read(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



307
308
309
# File 'sandbox.rb', line 307

def deny_read(path:, type: :literal)
  add_rule allow: false, operation: "file-read*", filter: path_filter(path, type)
end

#deny_read_homevoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'sandbox.rb', line 317

def deny_read_home
  require "trust"

  home = Pathname(Dir.home(ENV.fetch("USER"))).realpath
  readable_paths = [
    HOMEBREW_PREFIX,
    HOMEBREW_REPOSITORY,
    HOMEBREW_CACHE,
    HOMEBREW_LOGS,
    HOMEBREW_TEMP,
    ENV.fetch("GITHUB_WORKSPACE", nil),
    ENV.fetch("RUNNER_WORKSPACE", nil),
    ENV.fetch("RUNNER_TEMP", nil),
    Homebrew::Trust.trust_file,
    *home_write_paths.select { |path| File.exist?(path) },
  ].compact.flat_map do |path|
    path = Pathname(path)
    [path.expand_path, (path.realpath if path.exist?)].compact
  end
  if readable_paths.any? { |path| path.ascend.include?(home) }
    # When Homebrew or CI needs some `$HOME` paths to stay readable, deny only
    # well-known credential and personal-data paths instead of enumerating all
    # of `$HOME`.
    [
      ".ssh",
      ".aws",
      ".azure",
      ".boto",
      ".docker",
      ".config/fish",
      ".config/gh",
      ".config/gcloud",
      ".config/huggingface",
      ".config/pip",
      ".config/pypoetry",
      ".config/rclone",
      ".config/containers/auth.json",
      ".config/composer/auth.json",
      ".config/sops/age/keys.txt",
      ".gnupg",
      ".git-credentials",
      ".gitconfig",
      ".gsutil",
      ".kube",
      ".netrc",
      ".npmrc",
      ".yarnrc",
      ".yarnrc.yml",
      ".pnpmrc",
      ".bunfig.toml",
      ".pypirc",
      ".pip",
      ".poetry",
      ".local/share/pypoetry",
      ".gem/credentials",
      ".bundle/config",
      ".cargo/credentials",
      ".cargo/credentials.toml",
      ".composer/auth.json",
      ".condarc",
      ".m2/settings.xml",
      ".gradle/gradle.properties",
      ".sbt/1.0/credentials.sbt",
      ".terraform.d/credentials.tfrc.json",
      ".pulumi/credentials.json",
      ".oci/config",
      ".huggingface/token",
      ".cache/huggingface/token",
      ".claude",
      ".claude.json",
      ".kiro",
      ".bash_login",
      ".bash_logout",
      ".bash_profile",
      ".bashrc",
      ".bash_history",
      ".profile",
      ".zlogin",
      ".zlogout",
      ".zprofile",
      ".zshenv",
      ".zshrc",
      ".zsh_history",
      ".python_history",
      ".mysql_history",
      ".psql_history",
      ".env",
      ".env.local",
      "Documents",
      "Movies",
      "Music",
      "Pictures",
      "Library/Keychains",
      "Library/Mobile Documents",
      "Library/CloudStorage",
      "Dropbox",
      "Google Drive",
      "OneDrive",
    ].each do |path|
      path = home/path
      next unless path.exist?

      path = path.realpath
      next unless path.ascend.include?(home)

      if (readable_path = readable_paths.find { |required_path| required_path.ascend.include?(path) })
        opoo <<~EOS
          The sandbox cannot prevent formulae from reading:
            #{path}
          because this required path is inside it:
            #{readable_path}
          Formulae may access personal data in this directory.
        EOS
        next
      end

      deny_read_path path
    rescue Errno::ENOENT
      nil
    end
    return
  end

  deny_read_path home
end

#deny_read_path(path) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



312
313
314
# File 'sandbox.rb', line 312

def deny_read_path(path)
  deny_read path:, type: :subpath
end

#deny_write(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



459
460
461
# File 'sandbox.rb', line 459

def deny_write(path:, type: :literal)
  add_rule allow: false, operation: "file-write*", filter: path_filter(path, type)
end

#deny_write_homebrew_repositoryvoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



522
523
524
525
526
527
528
529
530
# File 'sandbox.rb', line 522

def deny_write_homebrew_repository
  deny_write path: HOMEBREW_ORIGINAL_BREW_FILE
  if HOMEBREW_PREFIX.to_s == HOMEBREW_REPOSITORY.to_s
    deny_write_path HOMEBREW_LIBRARY
    deny_write_path HOMEBREW_REPOSITORY/".git"
  else
    deny_write_path HOMEBREW_REPOSITORY
  end
end

#deny_write_path(path) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



477
478
479
# File 'sandbox.rb', line 477

def deny_write_path(path)
  deny_write path:, type: :subpath
end

#path_filter(path, type) ⇒ SandboxPathFilter

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (SandboxPathFilter)


641
642
643
644
645
646
647
648
649
650
651
652
653
654
# File 'sandbox.rb', line 641

def path_filter(path, type)
  # Any character is allowed: the OS-specific renderer quotes paths safely
  # (the seatbelt renderer escapes the `"` and `\` string delimiters; the
  # Linux sandbox passes each path as a separate argument), so even paths
  # with spaces, parentheses, quotes, backslashes or newlines are expressible.
  filter_path = case type
  when :regex   then path.to_s
  when :subpath, :literal
    expand_realpath(Pathname.new(path)).to_s
  else raise ArgumentError, "Invalid path filter type: #{type}"
  end

  SandboxPathFilter.new(path: filter_path, type:)
end

#record_log(file) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



282
283
284
# File 'sandbox.rb', line 282

def record_log(file)
  @logfile = file
end

#run(*args) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
# File 'sandbox.rb', line 543

def run(*args)
  Dir.mktmpdir("homebrew-sandbox", HOMEBREW_TEMP) do |tmpdir|
    allow_network path: File.join(tmpdir, "socket"), type: :literal if allow_network_for_error_pipe?
    @start = T.let(Time.now, T.nilable(Time))

    begin
      command = sandbox_command(args, tmpdir)
      # Start sandbox in a pseudoterminal to prevent access of the parent terminal.
      PTY.open do |controller, worker|
        # Set the PTY's window size to match the parent terminal.
        # Some formula tests are sensitive to the terminal size and fail if this is not set.
        winch = proc do |_sig|
          controller.winsize = if $stdout.tty?
            # We can only use IO#winsize if the IO object is a TTY.
            $stdout.winsize
          else
            # Otherwise, default to tput, if available.
            # This relies on ncurses rather than the system's ioctl.
            [Utils.popen_read("tput", "lines").to_i, Utils.popen_read("tput", "cols").to_i]
          end
        end

        write_to_pty = proc do
          # Don't hang if stdin is not able to be used - throw EIO instead.
          old_ttin = trap(:TTIN, "IGNORE")

          # Update the window size whenever the parent terminal's window size changes.
          old_winch = trap(:WINCH, &winch)
          winch.call(nil)

          stdin_thread = Thread.new do
            IO.copy_stream($stdin, controller)
          rescue Errno::EIO
            # stdin is unavailable - move on.
          end

          stdout_thread = Thread.new do
            copy_pty_output(controller)
          end

          Utils.safe_fork(directory: tmpdir, yield_parent: true) do |error_pipe|
            if error_pipe
              # Child side
              Process.setsid
              controller.close
              worker.ioctl(self.class.terminal_ioctl_request, 0) # Make this the controlling terminal.

              ensure_child_tty_available

              # Move into a non-denied directory before `exec` so subsequent
              # `getcwd(3)` calls (which walk every parent) never cross a
              # `deny_read_home` path inherited from the caller's CWD.
              Dir.chdir(tmpdir)

              worker.close_on_exec = true
              apply_sandbox
              exec(*command, in: worker, out: worker, err: worker) # And map everything to the PTY.
            else
              # Parent side
              worker.close
            end
          end
        rescue ChildProcessError => e
          raise ErrorDuringExecution.new(command, status: e.status)
        ensure
          stdin_thread&.kill
          stdout_thread&.kill
          trap(:TTIN, old_ttin)
          trap(:WINCH, old_winch)
        end

        if $stdin.tty?
          # If stdin is a TTY, use io.raw to set stdin to a raw, passthrough
          # mode while we copy the input/output of the process spawned in the
          # PTY. After we've finished copying to/from the PTY process, io.raw
          # will restore the stdin TTY to its original state.
          begin
            # Ignore SIGTTOU as setting raw mode will hang if the process is in the background.
            old_ttou = trap(:TTOU, "IGNORE")
            $stdin.raw(&write_to_pty)
          ensure
            trap(:TTOU, old_ttou)
          end
        else
          write_to_pty.call
        end
      end
    rescue
      @failed = true
      raise
    ensure
      record_sandbox_log
    end
  end
end