Skip to content
Kward Search API index

Class: Kward::Sandbox::LinuxBubblewrapRunner

Inherits:
CommandRunner show all
Defined in:
lib/kward/sandbox/linux_bubblewrap_runner.rb

Overview

Runs a command in a Bubblewrap mount namespace on Linux.

Instance Attribute Summary

Attributes inherited from CommandRunner

#capabilities, #policy

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(policy:, executable: self.class.executable, capabilities: self.class.capabilities(executable: executable)) ⇒ LinuxBubblewrapRunner

Returns a new instance of LinuxBubblewrapRunner.

Raises:

  • (ArgumentError)


31
32
33
34
35
36
# File 'lib/kward/sandbox/linux_bubblewrap_runner.rb', line 31

def initialize(policy:, executable: self.class.executable, capabilities: self.class.capabilities(executable: executable))
  raise ArgumentError, "Linux Bubblewrap is unavailable: #{capabilities.reason}" unless capabilities.available?

  super(policy:, capabilities:)
  @executable = executable
end

Class Method Details

.capabilities(platform: RUBY_PLATFORM, executable: self.executable) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/kward/sandbox/linux_bubblewrap_runner.rb', line 18

def self.capabilities(platform: RUBY_PLATFORM, executable: self.executable)
  available = platform.to_s.include?("linux") && !executable.to_s.empty?
  return Capabilities.new(available: true, filesystem_enforced: true, child_network_enforced: true, backend: "linux_bubblewrap") if available

  Capabilities.new(
    available: false,
    filesystem_enforced: false,
    child_network_enforced: false,
    backend: "linux_bubblewrap",
    reason: "Bubblewrap is unavailable"
  )
end

.executableObject



11
12
13
14
15
16
# File 'lib/kward/sandbox/linux_bubblewrap_runner.rb', line 11

def self.executable
  ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).filter_map do |directory|
    path = File.expand_path("bwrap", directory)
    path if File.file?(path) && File.executable?(path)
  end.first
end

Instance Method Details

#command_argv(command, cwd:, temporary_root: nil) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/kward/sandbox/linux_bubblewrap_runner.rb', line 57

def command_argv(command, cwd:, temporary_root: nil)
  temporary_root ||= Dir.tmpdir
  workspace_root = policy.workspace_root
  writable_roots = policy.command_writable_roots + [File.realpath(temporary_root)]
  argv = [@executable, "--die-with-parent", "--new-session", "--ro-bind", "/", "/", "--dev", "/dev", "--proc", "/proc"]
  argv << "--unshare-net" unless policy.child_network_allowed?
  argv.concat(["--bind", workspace_root, workspace_root]) if policy.workspace_write?
  writable_roots.reject { |path| path == workspace_root }.each { |path| argv.concat(["--bind", path, path]) }
  argv.concat(["--ro-bind", File.join(workspace_root, ".git"), File.join(workspace_root, ".git")]) if policy.protect_git_metadata? && File.exist?(File.join(workspace_root, ".git"))
  argv.concat(["--setenv", "TMPDIR", temporary_root, "--setenv", "TMP", temporary_root, "--setenv", "TEMP", temporary_root])
  argv.concat(["--chdir", cwd.to_s, "--", "/bin/sh", "-lc", command.to_s])
end

#run(command, cwd:, timeout_seconds:, max_output_bytes:, cancellation: nil, &block) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/kward/sandbox/linux_bubblewrap_runner.rb', line 38

def run(command, cwd:, timeout_seconds:, max_output_bytes:, cancellation: nil, &block)
  temporary_root = Dir.mktmpdir("kward-sandbox")
  environment = Environment.command_worker(temporary_root)

  LocalCommandRunner.new(
    timeout_seconds: timeout_seconds,
    max_output_bytes: max_output_bytes
  ).run(
    *command_argv(command, cwd: cwd, temporary_root: temporary_root),
    env: environment,
    cwd: cwd,
    cancellation: cancellation,
    unsetenv_others: true,
    &block
  )
ensure
  FileUtils.remove_entry(temporary_root) if temporary_root && File.exist?(temporary_root)
end