exec_sandbox 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.2.0
data/exec_sandbox.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "exec_sandbox"
8
- s.version = "0.1.3"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Victor Costan"]
@@ -38,6 +38,7 @@ Gem::Specification.new do |s|
38
38
  "spec/exec_sandbox/wait4_spec.rb",
39
39
  "spec/fixtures/buffer.rb",
40
40
  "spec/fixtures/churn.rb",
41
+ "spec/fixtures/count.rb",
41
42
  "spec/fixtures/duplicate.rb",
42
43
  "spec/fixtures/exit_arg.rb",
43
44
  "spec/fixtures/fork.rb",
@@ -49,7 +50,7 @@ Gem::Specification.new do |s|
49
50
  s.homepage = "http://github.com/pwnall/exec_sandbox"
50
51
  s.licenses = ["MIT"]
51
52
  s.require_paths = ["lib"]
52
- s.rubygems_version = "1.8.11"
53
+ s.rubygems_version = "1.8.12"
53
54
  s.summary = "Run foreign binaries using POSIX sandboxing features"
54
55
 
55
56
  if s.respond_to? :specification_version then
data/lib/exec_sandbox.rb CHANGED
@@ -6,6 +6,7 @@ require 'English'
6
6
  require 'etc'
7
7
  require 'fcntl'
8
8
  require 'fileutils'
9
+ require 'set'
9
10
  require 'tempfile'
10
11
  require 'tmpdir'
11
12
 
@@ -92,8 +92,11 @@ class Sandbox
92
92
  # @option options [String] :out path to a file that is set as the child's
93
93
  # stdout; if not set, the child will receive the write end of a pipe whose
94
94
  # contents is returned in :out_data
95
+ # @option options [Symbol] :err :none closes the child's stderr, :out
96
+ # redirects the child's stderr to stdout; by default, the child's stderr
97
+ # is the same as the parent's
95
98
  # @return [Hash] the result of {Wait4#wait4}, plus an :out_data key if no :out
96
- # option is given
99
+ # option is given
97
100
  def run(command, options = {})
98
101
  limits = options[:limits] || {}
99
102
 
@@ -113,7 +116,14 @@ class Sandbox
113
116
  out_rd, out_wr = IO.pipe
114
117
  io[:out] = out_wr
115
118
  end
116
- io[:err] = STDERR unless options[:no_stderr]
119
+ case options[:err]
120
+ when :out
121
+ io[:err] = STDOUT
122
+ when :none
123
+ # Don't set io[:err], so the child's stderr will be closed.
124
+ else
125
+ io[:err] = STDERR
126
+ end
117
127
 
118
128
  pid = ExecSandbox::Spawn.spawn command, io, @principal, limits
119
129
  # Close the pipe ends that are meant to be used in the child.
@@ -28,32 +28,44 @@ module Spawn
28
28
  # @param [Hash] io associates file descriptors with IO objects or file paths;
29
29
  # all file descriptors not covered by io will be closed
30
30
  def self.limit_io(io)
31
+ # Sort the list of redirections by file descriptor number.
32
+ redirects = []
31
33
  [:in, :out, :err].each_with_index do |sym, fd_num|
32
- if target = io.delete(sym)
33
- io[fd_num] = target
34
+ if target = io[sym]
35
+ redirects << [fd_num, redirects.length, target]
34
36
  end
35
37
  end
36
38
  io.each do |k, v|
37
- if v.respond_to?(:fileno)
38
- if v.fileno != k
39
- LibC.close k
40
- LibC.dup2 v.fileno, k
39
+ if k.kind_of? Integer
40
+ redirects << [k, redirects.length, v]
41
+ end
42
+ end
43
+
44
+ # Perform the redirections.
45
+ redirects.sort!
46
+ redirects.each do |fd_num, _, target|
47
+ if target.respond_to?(:fileno)
48
+ # IO stream.
49
+ if target.fileno != fd_num
50
+ LibC.close fd_num
51
+ LibC.dup2 target.fileno, fd_num
41
52
  end
42
53
  else
43
- LibC.close k
44
- open_fd = IO.sysopen(v, 'r+')
45
- if open_fd != k
46
- LibC.dup2 open_fd, k
54
+ # Filename string.
55
+ LibC.close fd_num
56
+ open_fd = IO.sysopen(target, 'r+')
57
+ if open_fd != fd_num
58
+ LibC.dup2 open_fd, fd_num
47
59
  LibC.close open_fd
48
60
  end
49
61
  end
50
62
  end
51
63
 
52
- # Close all file descriptors.
64
+ # Close all file descriptors not in the redirection table.
65
+ redirected_fds = Set.new redirects.map(&:first)
53
66
  max_fd = LibC.getdtablesize
54
67
  0.upto(max_fd) do |fd|
55
- next if io[fd]
56
- LibC.close fd
68
+ LibC.close fd unless redirected_fds.include?(fd)
57
69
  end
58
70
  end
59
71
 
@@ -8,23 +8,44 @@ describe ExecSandbox::Sandbox do
8
8
  @temp_in.close
9
9
  @temp_out = Tempfile.new 'exec_sandbox_rspec'
10
10
  @temp_out.close
11
-
12
- ExecSandbox.use do |s|
13
- @result = s.run bin_fixture(:duplicate), :in => @temp_in.path,
14
- :out => @temp_out.path
15
- end
16
11
  end
17
12
  after do
18
13
  @temp_in.unlink
19
14
  @temp_out.unlink
20
15
  end
21
16
 
22
- it 'should not crash' do
23
- @result[:exit_code].should == 0
17
+ describe 'duplicate.rb' do
18
+ before do
19
+ ExecSandbox.use do |s|
20
+ @result = s.run bin_fixture(:duplicate), :in => @temp_in.path,
21
+ :out => @temp_out.path
22
+ end
23
+ end
24
+
25
+ it 'should not crash' do
26
+ @result[:exit_code].should == 0
27
+ end
28
+
29
+ it 'should produce the correct result' do
30
+ File.read(@temp_out.path).should == "I/O test\nI/O test\n"
31
+ end
24
32
  end
25
-
26
- it 'should produce the correct result' do
27
- File.read(@temp_out.path).should == "I/O test\nI/O test\n"
33
+
34
+ describe 'count.rb' do
35
+ before do
36
+ ExecSandbox.use do |s|
37
+ @result = s.run [bin_fixture(:count), '9'], :in => @temp_in.path,
38
+ :out => @temp_out.path, :err => :out
39
+ end
40
+ end
41
+
42
+ it 'should not crash' do
43
+ @result[:exit_code].should == 0
44
+ end
45
+
46
+ it 'should produce the correct result' do
47
+ File.read(@temp_out.path).should == (1..9).map { |i| "#{i}\n" }.join('')
48
+ end
28
49
  end
29
50
  end
30
51
 
@@ -61,6 +82,22 @@ describe ExecSandbox::Sandbox do
61
82
  @result[:out_data].should == "S" * buffer_size
62
83
  end
63
84
  end
85
+
86
+ describe 'count.rb' do
87
+ before do
88
+ ExecSandbox.use do |s|
89
+ @result = s.run [bin_fixture(:count), '9'], :err => :out
90
+ end
91
+ end
92
+
93
+ it 'should not crash' do
94
+ @result[:exit_code].should == 0
95
+ end
96
+
97
+ it 'should produce the correct result' do
98
+ @result[:out_data].should == (1..9).map { |i| "#{i}\n" }.join('')
99
+ end
100
+ end
64
101
  end
65
102
 
66
103
 
@@ -69,6 +69,35 @@ describe ExecSandbox::Spawn do
69
69
  @status[:exit_code].should_not == 0
70
70
  end
71
71
  end
72
+
73
+ shared_examples_for 'count.rb' do
74
+ it 'should not crash' do
75
+ @status[:exit_code].should == 0
76
+ end
77
+
78
+ it 'should write successfully' do
79
+ @temp_out.open
80
+ begin
81
+ @temp_out.read.should == (1..9).map { |i| "#{i}\n" }.join('')
82
+ ensure
83
+ @temp_out.close
84
+ end
85
+ end
86
+ end
87
+
88
+ describe 'with file descriptor and stderr redirected to stdout' do
89
+ before do
90
+ File.open(@temp_in.path, 'r') do |in_io|
91
+ File.open(@temp_out.path, 'w') do |out_io|
92
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:count), '9'],
93
+ {:in => in_io, :out => out_io, :err => STDOUT}
94
+ @status = ExecSandbox::Wait4.wait4 pid
95
+ end
96
+ end
97
+ end
98
+
99
+ it_behaves_like 'count.rb'
100
+ end
72
101
  end
73
102
 
74
103
  describe '#spawn principal' do
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Counts from 1 to the first argument, and writes each number on a separate
4
+ # line. Odd numbers go to STDOUT, even numbers go to STDERR.
5
+ # on both STDOUT and STDERR.
6
+
7
+ limit = ARGV[0].to_i
8
+ 1.upto(limit) do |i|
9
+ file = (i % 2 == 0) ? STDERR : STDOUT
10
+ file.write "#{i}\n"
11
+ file.flush
12
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exec_sandbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2011-12-20 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi
16
- requirement: &15824600 !ruby/object:Gem::Requirement
16
+ requirement: &26627980 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.0.11
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *15824600
24
+ version_requirements: *26627980
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rdoc
27
- requirement: &15824120 !ruby/object:Gem::Requirement
27
+ requirement: &26627380 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '3.10'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *15824120
35
+ version_requirements: *26627380
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &15823640 !ruby/object:Gem::Requirement
38
+ requirement: &26626760 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 2.6.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *15823640
46
+ version_requirements: *26626760
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: yard
49
- requirement: &15823160 !ruby/object:Gem::Requirement
49
+ requirement: &26626260 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 0.7.2
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *15823160
57
+ version_requirements: *26626260
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: yard-rspec
60
- requirement: &15822680 !ruby/object:Gem::Requirement
60
+ requirement: &26625740 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0.1'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *15822680
68
+ version_requirements: *26625740
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
- requirement: &15822200 !ruby/object:Gem::Requirement
71
+ requirement: &26625220 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 1.0.21
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *15822200
79
+ version_requirements: *26625220
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: jeweler
82
- requirement: &15821720 !ruby/object:Gem::Requirement
82
+ requirement: &26624740 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: 1.6.4
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *15821720
90
+ version_requirements: *26624740
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: rcov
93
- requirement: &15821240 !ruby/object:Gem::Requirement
93
+ requirement: &26624240 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,7 +98,7 @@ dependencies:
98
98
  version: '0'
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *15821240
101
+ version_requirements: *26624240
102
102
  description: Temporary users and groups, rlimits
103
103
  email: costan@gmail.com
104
104
  executables: []
@@ -128,6 +128,7 @@ files:
128
128
  - spec/exec_sandbox/wait4_spec.rb
129
129
  - spec/fixtures/buffer.rb
130
130
  - spec/fixtures/churn.rb
131
+ - spec/fixtures/count.rb
131
132
  - spec/fixtures/duplicate.rb
132
133
  - spec/fixtures/exit_arg.rb
133
134
  - spec/fixtures/fork.rb
@@ -150,7 +151,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
151
  version: '0'
151
152
  segments:
152
153
  - 0
153
- hash: -3240483873242314683
154
+ hash: -1397950588643828772
154
155
  required_rubygems_version: !ruby/object:Gem::Requirement
155
156
  none: false
156
157
  requirements:
@@ -159,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
160
  version: '0'
160
161
  requirements: []
161
162
  rubyforge_project:
162
- rubygems_version: 1.8.11
163
+ rubygems_version: 1.8.12
163
164
  signing_key:
164
165
  specification_version: 3
165
166
  summary: Run foreign binaries using POSIX sandboxing features