Class: ExecSandbox::Sandbox

Inherits:
Object
  • Object
show all
Defined in:
lib/exec_sandbox/sandbox.rb

Overview

Manages sandboxed processes.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(admin) ⇒ Sandbox

Empty sandbox.

Parameters:

  • admin (String)

    the name of a user who will be able to peek into the sandbox



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 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

Instance Attribute Details

#pathObject (readonly)

The path to the sandbox’s working directory.



7
8
9
# File 'lib/exec_sandbox/sandbox.rb', line 7

def path
  @path
end

#user_nameObject (readonly)

The un-privileged user that execs sandboxed binaries.



10
11
12
# File 'lib/exec_sandbox/sandbox.rb', line 10

def user_name
  @user_name
end

Class Method Details

.cleanupArray<String>

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.

Returns:

  • (Array<String>)

    the names of the deleted users



166
167
168
# File 'lib/exec_sandbox/sandbox.rb', line 166

def self.cleanup
  Users.destroy_temps
end

Instance Method Details

#closeObject

Removes the files and temporary user associated with this sandbox.



148
149
150
151
152
# File 'lib/exec_sandbox/sandbox.rb', line 148

def close
  return if @destroyed
  ExecSandbox::Users.destroy @user_name
  @destroyed = true
end

#finalizeObject

Cleans up when the sandbox object is garbage-collected.



155
156
157
# File 'lib/exec_sandbox/sandbox.rb', line 155

def finalize
  close
end

#pull(from, to) ⇒ String

Copies a file or directory from the sandbox.

Parameters:

  • from (String)

    relative path to the sandbox file or directory

  • to (String)

    path where the file/directory will be copied

  • options (Hash)

    tweaks the permissions and the path inside the sandbox

Returns:

  • (String)

    the path to the copied file / directory outside the sandbox, or nil if the file / directory does not exist inside the sandbox



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 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 = {}) ⇒ String

Copies a file or directory to the sandbox.

Parameters:

  • from (String)

    path to the file or directory to be copied

  • options (Hash) (defaults to: {})

    tweaks the permissions and the path inside the sandbox

Options Hash (options):

  • :to (String)

    the path inside the sandbox where the file or directory will be copied (defaults to the name of the source)

  • :read_only (Boolean)

    if true, the sandbox user will not be able to write to the file / directory

Returns:

  • (String)

    the absolute path to the copied file / directory inside the sandbox



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 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 = {}) ⇒ Hash

Runs a command in the sandbox.

Parameters:

  • command (Array, String)

    to be run; use an array to pass arguments to the command

  • options (Hash) (defaults to: {})

    stdin / stdout redirection and resource limitations

Options Hash (options):

  • :limits (Hash)
  • :in (String)

    path to a file that is set as the child’s stdin

  • :in_data (String)

    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

  • :out (String)

    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

  • :err (Symbol)

    :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

Returns:

  • (Hash)

    the result of Wait4.wait4, plus an :out_data key if no :out option is given



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# 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