Module: ExecSandbox::Spawn

Defined in:
lib/exec_sandbox/spawn.rb

Overview

Manages sandboxed processes.

Defined Under Namespace

Modules: LibC, RubyVM

Class Method Summary collapse

Class Method Details

._setrlimit(limit, value) ⇒ Object

Wrapper for Process.setrlimit that eats exceptions.



147
148
149
150
151
152
153
# File 'lib/exec_sandbox/spawn.rb', line 147

def self._setrlimit(limit, value)
  begin
    Process.setrlimit limit, value, value
  rescue Errno::EPERM
    # The call failed, probably because the limit is already lower than this.
  end
end

.limit_io(io) ⇒ Object

Constraints the available file descriptors.

Parameters:

  • io (Hash)

    associates file descriptors with IO objects or file paths; all file descriptors not covered by io will be closed



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/exec_sandbox/spawn.rb', line 30

def self.limit_io(io)
  # Sort the list of redirections by file descriptor number.
  redirects = []
  [:in, :out, :err].each_with_index do |sym, fd_num|
    if target = io[sym]
      redirects << [fd_num, redirects.length, target]
    end
  end
  io.each do |k, v|
    if k.kind_of? Integer
      redirects << [k, redirects.length, v]
    end
  end

  # Perform the redirections.
  redirects.sort!
  redirects.each do |fd_num, _, target|
    if target.respond_to?(:fileno)
      # IO stream.
      if target.fileno != fd_num
        LibC.close fd_num
        LibC.dup2 target.fileno, fd_num
      end
    else
      # Filename string.
      LibC.close fd_num
      open_fd = IO.sysopen(target, 'r+')
      if open_fd != fd_num
        LibC.dup2 open_fd, fd_num
        LibC.close open_fd
      end
    end
  end

  # Close all file descriptors not in the redirection table.
  redirected_fds = Set.new redirects.map(&:first)
  max_fd = LibC.getdtablesize
  max_fd.downto 0 do |fd|
    next if redirected_fds.include?(fd)

    next if RubyVM.rb_reserved_fd_p(fd) != 0
    LibC.close fd
  end
end

.limit_resources(limits) ⇒ Object

Constrains the resource usage of the current process.

Parameters:

  • limits (Hash{Symbol => Number})

    the constraints to be applied

Options Hash (limits):

  • :cpu (Fixnum)

    maximum CPU time (for best results, give it an extra second, and measure actual resource usage after the process completes)

  • :processes (Fixnum)

    number of processes that can be spawned by the user who owns this process (useful in conjunction with temporary users)

  • :file_size (Fixnum)

    maximum size of a file created by the process; the process can still fill the disk by creating many files of this size

  • :open_files (Fixnum)

    maximum number of open files; remember that any process uses 3 open files for STDIN, STDOUT, and STDERR

  • :data (Fixnum)

    maximum data segment size (static data plus heap) and stack; allow slack for the libraries used by the process; mostly useful to prevent a process from freezing the machine by pushing everything into swap



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/exec_sandbox/spawn.rb', line 124

def self.limit_resources(limits)
  if limits[:cpu]
    _setrlimit Process::RLIMIT_CPU, limits[:cpu]
  end
  if limits[:processes]
    _setrlimit Process::RLIMIT_NPROC, limits[:processes]
  end
  if limits[:file_size]
    _setrlimit Process::RLIMIT_FSIZE, limits[:file_size]
  end
  if limits[:open_files]
    _setrlimit Process::RLIMIT_NOFILE, limits[:open_files]
  end
  if limits[:data]
    _setrlimit Process::RLIMIT_AS, limits[:data]
    _setrlimit Process::RLIMIT_DATA, limits[:data]
    _setrlimit Process::RLIMIT_STACK, limits[:data]
    _setrlimit Process::RLIMIT_MEMLOCK, limits[:data]
    _setrlimit Process::RLIMIT_RSS, limits[:data]
  end
end

.set_principal(principal) ⇒ Object

Sets the process’ principal for access control.

Parameters:

  • principal (Hash)

    information about the process’ principal

Options Hash (principal):

  • :dir (String)

    the process’ working directory

  • :uid (Fixnum)

    the new user ID

  • :gid (Fixnum)

    the new group ID



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/exec_sandbox/spawn.rb', line 81

def self.set_principal(principal)
  Dir.chdir principal[:dir] if principal[:dir]

  if principal[:gid]
    begin
      Process::Sys.setresgid principal[:gid], principal[:gid], principal[:gid]
    rescue NotImplementedError
      Process::Sys.setgid principal[:gid]
    end
  end
  if principal[:uid]
    begin
      Process.initgroups Etc.getpwuid(principal[:uid]).name,
                         principal[:gid] || Process.gid
    rescue NotImplementedError
    end

    begin
      Process::Sys.setresuid principal[:uid], principal[:uid], principal[:uid]
    rescue NotImplementedError
      Process::Sys.setuid principal[:uid]
    end
  end
end

.spawn(command, io = {}, principal = {}, resources = {}) ⇒ Fixnum

Spawns a child process.

Parameters:

  • command (String, Array)

    the command to be executed via exec

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

    see limit_io

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

    the principal for the new process

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

    see limit_resources

Returns:

  • (Fixnum)

    the child’s PID



13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/exec_sandbox/spawn.rb', line 13

def self.spawn(command, io = {}, principal = {}, resources = {})
  fork do
    limit_io io
    limit_resources resources
    set_principal principal
    if command.respond_to? :to_str
      Process.exec command
    else
      Process.exec *command
    end
  end
end