class ExecSandbox::Sandbox

Manages sandboxed processes.

Attributes

path[R]

The path to the sandbox’s working directory.

user_name[R]

The un-privileged user that execs sandboxed binaries.

Public Class Methods

cleanup() click to toggle source

Removes temporary users created by old sandboxes.

Sandboxes usually clean up after themselves. For the rare circumstances where that doesn’t happen (VM crash, Ctrl+C, SIGKILL), this method finds and cleans up the temporary users created for sandboxing.

@return [Array<String>] the names of the deleted users

# File lib/exec_sandbox/sandbox.rb, line 166
def self.cleanup
  Users.destroy_temps
end
new(admin) click to toggle source

Empty sandbox.

@param [String] admin the name of a user who will be able to peek into the

sandbox
# File lib/exec_sandbox/sandbox.rb, line 16
def initialize(admin)
  @user_name = ExecSandbox::Users.temp
  user_pwd = Etc.getpwnam @user_name
  @user_uid = user_pwd.uid
  @user_gid = user_pwd.gid
  @path = user_pwd.dir
  @admin_name = admin
  admin_pwd = Etc.getpwnam(@admin_name)
  @admin_uid = admin_pwd.uid
  @admin_gid = admin_pwd.gid
  @destroyed = false
  
  # principal argument for Spawn.spawn()
  @principal = { uid: @user_uid, gid: @user_gid, dir: @path }
end

Public Instance Methods

close() click to toggle source

Removes the files and temporary user associated with this sandbox.

# File lib/exec_sandbox/sandbox.rb, line 148
def close
  return if @destroyed
  ExecSandbox::Users.destroy @user_name
  @destroyed = true
end
finalize() click to toggle source

Cleans up when the sandbox object is garbage-collected.

# File lib/exec_sandbox/sandbox.rb, line 155
def finalize
  close
end
pull(from, to) click to toggle source

Copies a file or directory from the sandbox.

@param [String] from relative path to the sandbox file or directory @param [String] to path where the file/directory will be copied @param [Hash] options tweaks the permissions and the path inside the sandbox @return [String] the path to the copied file / directory outside the

sandbox, or nil if the file / directory does not exist
inside the sandbox
# File lib/exec_sandbox/sandbox.rb, line 66
def pull(from, to)
  from = File.join @path, from
  return nil unless File.exist? from
  
  FileUtils.cp_r from, to
  FileUtils.chmod_R 0770, to
  FileUtils.chown_R @admin_uid, @admin_gid, to
  # NOTE: making a file / directory read-only is useless -- the sandboxed
  #       process can replace the file with another copy of the file; this can
  #       be worked around by noting the inode number of the protected file /
  #       dir, and making a hard link to it somewhere else so the inode won't
  #       be reused.
  
  to
end
push(from, options = {}) click to toggle source

Copies a file or directory to the sandbox.

@param [String] from path to the file or directory to be copied @param [Hash] options tweaks the permissions and the path inside the sandbox @option options [String] :to the path inside the sandbox where the file or

directory will be copied (defaults to the name of the source)

@option options [Boolean] :read_only if true, the sandbox user will not be

able to write to the file / directory

@return [String] the absolute path to the copied file / directory inside the

sandbox
# File lib/exec_sandbox/sandbox.rb, line 42
def push(from, options = {})
  to = File.join @path, (options[:to] || File.basename(from))
  FileUtils.cp_r from, to
  
  permissions = options[:read_only] ? 0770 : 0750
  FileUtils.chmod_R permissions, to
  FileUtils.chown_R @admin_uid, @user_gid, to
  # NOTE: making a file / directory read-only is useless -- the sandboxed
  #       process can replace the file with another copy of the file; this can
  #       be worked around by noting the inode number of the protected file /
  #       dir, and making a hard link to it somewhere else so the inode won't
  #       be reused.
  
  to
end
run(command, options = {}) click to toggle source

Runs a command in the sandbox.

@param [Array, String] command to be run; use an array to pass arguments to

the command

@param [Hash] options stdin / stdout redirection and resource limitations @option options [Hash] :limits see {Spawn.limit_resources} @option options [String] :in path to a file that is set as the child’s stdin @option options [String] :in_data contents to be written to a pipe that is

set as the child's stdin; if neither :in nor :in_data are specified, the
child will receive the read end of an empty pipe

@option options [String] :out path to a file that is set as the child’s

stdout; if not set, the child will receive the write end of a pipe whose
contents is returned in :out_data

@option options [Symbol] :err :none closes the child’s stderr, :out

redirects the child's stderr to stdout; by default, the child's stderr
is the same as the parent's

@return [Hash] the result of {Wait4.wait4}, plus an :out_data key if no :out

option is given
# File lib/exec_sandbox/sandbox.rb, line 100
def run(command, options = {})
  limits = options[:limits] || {}
  
  io = {}
  if options[:in]
    io[:in] = options[:in]
    in_rd = nil
  else
    in_rd, in_wr = IO.pipe
    in_wr.write options[:in_data] if options[:in_data]
    in_wr.close
    io[:in] = in_rd
  end
  if options[:out]
    io[:out] = options[:out]
  else
    out_rd, out_wr = IO.pipe
    io[:out] = out_wr
  end
  case options[:err]
  when :out
    io[:err] = STDOUT
  when :none
    # Don't set io[:err], so the child's stderr will be closed.
  else
    io[:err] = STDERR
  end
  
  pid = ExecSandbox::Spawn.spawn command, io, @principal, limits
  # Close the pipe ends that are meant to be used in the child.
  in_rd.close if in_rd
  out_wr.close if out_wr
  
  # Collect information about the child.
  if out_rd
    out_pieces = []
    out_pieces << out_rd.read rescue nil
  end
  status = ExecSandbox::Wait4.wait4 pid
  if out_rd
    out_pieces << out_rd.read rescue nil
    out_rd.close
    status[:out_data] = out_pieces.join('')
  end
  status
end