exec_sandbox 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ # namespace
2
+ module ExecSandbox
3
+
4
+ # Interface to the wait4 system call using the ffi library.
5
+ module Wait4
6
+ # Waits for a process to end, and collects its exit status and resource usage.
7
+ #
8
+ # @param [Fixnum] pid the PID of the process to wait for; should be a child of
9
+ # this process
10
+ # @return [Hash] exit code and resource usage information
11
+ def self.wait4(pid)
12
+ status_ptr = FFI::MemoryPointer.new :int
13
+ rusage = ExecSandbox::Wait4::Rusage.new
14
+ returned_pid = LibC.wait4(pid, status_ptr, 0, rusage.pointer)
15
+ raise SystemCallError, FFI.errno if returned_pid < 0
16
+ status = { :bits => status_ptr.read_int }
17
+ status_ptr.free
18
+
19
+ signal_code = status[:bits] & 0x7f
20
+ status[:exit_code] = (signal_code != 0) ? -signal_code : status[:bits] >> 8
21
+ status[:user_time] = rusage[:ru_utime_sec] +
22
+ rusage[:ru_utime_usec] * 0.000_001
23
+ status[:system_time] = rusage[:ru_utime_sec] +
24
+ rusage[:ru_utime_usec] * 0.000_001
25
+ status[:rss] = rusage[:ru_maxrss] / 1024.0
26
+ return status
27
+ end
28
+
29
+ # Maps wait4 in libc.
30
+ module LibC
31
+ extend FFI::Library
32
+ ffi_lib FFI::Library::LIBC
33
+ attach_function :wait4, [:int, :pointer, :int, :pointer], :int,
34
+ :blocking => true
35
+ end # module ExecSandbox::Wait4::Libc
36
+
37
+ # Maps struct rusage in sys/resource.h, used by wait4.
38
+ class Rusage < FFI::Struct
39
+ # Total amount of user time used.
40
+ layout :ru_utime_sec, :time_t,
41
+ :ru_utime_usec, :suseconds_t,
42
+ # Total amount of system time used.
43
+ :ru_stime_sec, :time_t,
44
+ :ru_stime_usec, :suseconds_t,
45
+ # Maximum resident set size (in kilobytes).
46
+ :ru_maxrss, :long,
47
+ # Amount of sharing of text segment memory with other processes
48
+ # (kilobyte-seconds).
49
+ :ru_ixrss, :long,
50
+ # Amount of data segment memory used (kilobyte-seconds).
51
+ :ru_idrss, :long,
52
+ # Amount of stack memory used (kilobyte-seconds).
53
+ :ru_isrss, :long,
54
+ # Number of soft page faults (i.e. those serviced by reclaiming a page from
55
+ # the list of pages awaiting reallocation.
56
+ :ru_minflt, :long,
57
+ # Number of hard page faults (i.e. those that required I/O).
58
+ :ru_majflt, :long,
59
+ # Number of times a process was swapped out of physical memory.
60
+ :ru_nswap, :long,
61
+ # Number of input operations via the file system. Note: This and
62
+ # `ru_oublock' do not include operations with the cache.
63
+ :ru_inblock, :long,
64
+ # Number of output operations via the file system.
65
+ :ru_oublock, :long,
66
+ # Number of IPC messages sent.
67
+ :ru_msgsnd, :long,
68
+ # Number of IPC messages received.
69
+ :ru_msgrcv, :long,
70
+ # Number of signals delivered.
71
+ :ru_nsignals, :long,
72
+ # Number of voluntary context switches, i.e. because the process gave up the
73
+ # process before it had to (usually to wait for some resource to be
74
+ # available).
75
+ :ru_nvcsw, :long,
76
+ # Number of involuntary context switches, i.e. a higher priority process
77
+ # became runnable or the current process used up its time slice.
78
+ :ru_nivcsw, :long,
79
+ # Padding, so we don't crash if the struct gets ammended on newer OSes.
80
+ :padding, :uchar, 256
81
+ end # struct ExecSandbox::Wait4::Rusage
82
+
83
+ end # module ExecSandbox::Wait4
84
+
85
+ end # namespace ExecSandbox
@@ -0,0 +1,23 @@
1
+ # @title ExecSandbox - Run foreign binaries using POSIX sandboxing features
2
+ # @author Victor Costan
3
+
4
+ # Standard library
5
+ require 'English'
6
+ require 'etc'
7
+ require 'fcntl'
8
+ require 'fileutils'
9
+ require 'tempfile'
10
+ require 'tmpdir'
11
+
12
+ # Gems
13
+ require 'ffi'
14
+
15
+ # TODO(pwnall): documentation
16
+ module ExecSandbox
17
+ end # namespace ExecSandbox
18
+
19
+ # Code
20
+ require 'exec_sandbox/sandbox.rb'
21
+ require 'exec_sandbox/spawn.rb'
22
+ require 'exec_sandbox/users.rb'
23
+ require 'exec_sandbox/wait4.rb'
@@ -0,0 +1,138 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ExecSandbox::Sandbox do
4
+ describe 'IO redirection' do
5
+ before do
6
+ @temp_in = Tempfile.new 'exec_sandbox_rspec'
7
+ @temp_in.write "I/O test\n"
8
+ @temp_in.close
9
+ @temp_out = Tempfile.new 'exec_sandbox_rspec'
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
+ end
17
+ after do
18
+ @temp_in.unlink
19
+ @temp_out.unlink
20
+ end
21
+
22
+ it 'should not crash' do
23
+ @result[:exit_code].should == 0
24
+ end
25
+
26
+ it 'should produce the correct result' do
27
+ File.read(@temp_out.path).should == "I/O test\nI/O test\n"
28
+ end
29
+ end
30
+
31
+ describe 'pipe redirection' do
32
+ before do
33
+ ExecSandbox.use do |s|
34
+ @result = s.run bin_fixture(:duplicate), :in_data => "Pipe test\n"
35
+ end
36
+ end
37
+
38
+ it 'should not crash' do
39
+ @result[:exit_code].should == 0
40
+ end
41
+
42
+ it 'should produce the correct result' do
43
+ @result[:out_data].should == "Pipe test\nPipe test\n"
44
+ end
45
+ end
46
+
47
+
48
+ describe 'resource limitations' do
49
+ describe 'churn.rb' do
50
+ before do
51
+ @temp_out = Tempfile.new 'exec_sandbox_rspec'
52
+ @temp_out.close
53
+ end
54
+ after do
55
+ @temp_out.unlink
56
+ end
57
+
58
+ describe 'without limitations' do
59
+ before do
60
+ ExecSandbox.use do |s|
61
+ @result = s.run [bin_fixture(:churn), 'stdout', 3.to_s]
62
+ s.pull 'stdout', @temp_out.path
63
+ end
64
+ end
65
+
66
+ it 'should not crash' do
67
+ @result[:exit_code].should == 0
68
+ end
69
+
70
+ it 'should run for at least 2 seconds' do
71
+ (@result[:user_time] + @result[:system_time]).should > 2
72
+ end
73
+
74
+ it 'should output something' do
75
+ File.stat(@temp_out.path).size.should > 0
76
+ end
77
+ end
78
+
79
+ describe 'with CPU time limitation' do
80
+ before do
81
+ ExecSandbox.use do |s|
82
+ @result = s.run [bin_fixture(:churn), 'stdout', 3.to_s],
83
+ :limits => {:cpu => 1}
84
+ s.pull 'stdout', @temp_out.path
85
+ end
86
+ end
87
+
88
+ it 'should run for at least 0.5 seconds' do
89
+ (@result[:user_time] + @result[:system_time]).should >= 0.5
90
+ end
91
+
92
+ it 'should run for less than 2 seconds' do
93
+ (@result[:user_time] + @result[:system_time]).should < 2
94
+ end
95
+
96
+ it 'should not have a chance to output' do
97
+ File.stat(@temp_out.path).size.should == 0
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ describe '#push' do
104
+ let(:test_user) { Etc.getlogin }
105
+ let(:test_uid) { Etc.getpwnam(test_user).uid }
106
+ let(:test_gid) { Etc.getpwnam(test_user).gid }
107
+ let(:test_group) { Etc.getgrgid(test_gid).name }
108
+
109
+ before do
110
+ @sandbox = ExecSandbox.open test_user
111
+ end
112
+ after do
113
+ @sandbox.close
114
+ end
115
+
116
+ describe 'a file' do
117
+ before do
118
+ @to = @sandbox.push __FILE__
119
+ end
120
+
121
+ it 'should copy straight to the sandbox directory' do
122
+ File.dirname(@to).should == @sandbox.path
123
+ end
124
+
125
+ it 'should use the same file name' do
126
+ File.basename(@to).should == 'sandbox_spec.rb'
127
+ end
128
+
129
+ it "should set the file's owner to the admin" do
130
+ File.stat(@to).uid.should == test_uid
131
+ end
132
+
133
+ it "should not set the file's group to the admin" do
134
+ File.stat(@to).gid.should_not == test_gid
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,327 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ExecSandbox::Spawn do
4
+ let(:test_user) { Etc.getlogin }
5
+ let(:test_uid) { Etc.getpwnam(test_user).uid }
6
+ let(:test_gid) { Etc.getpwnam(test_user).gid }
7
+ let(:test_group) { Etc.getgrgid(test_gid).name }
8
+
9
+ describe '#spawn IO redirection' do
10
+ before do
11
+ @temp_in = Tempfile.new 'exec_sandbox_rspec'
12
+ @temp_in.write "Spawn IO test\n"
13
+ @temp_in.close
14
+ @temp_out = Tempfile.new 'exec_sandbox_rspec'
15
+ @temp_out.close
16
+ end
17
+ after do
18
+ @temp_in.unlink
19
+ @temp_out.unlink
20
+ end
21
+
22
+ shared_examples_for 'duplicate.rb' do
23
+ it 'should not crash' do
24
+ @status[:exit_code].should == 0
25
+ end
26
+
27
+ it 'should write successfully' do
28
+ @temp_out.open
29
+ begin
30
+ @temp_out.read.should == "Spawn IO test\nSpawn IO test\n"
31
+ ensure
32
+ @temp_out.close
33
+ end
34
+ end
35
+ end
36
+
37
+ describe 'with paths' do
38
+ before do
39
+ pid = ExecSandbox::Spawn.spawn bin_fixture(:duplicate),
40
+ {:in => @temp_in.path, :out => @temp_out.path}
41
+ @status = ExecSandbox::Wait4.wait4 pid
42
+ end
43
+
44
+ it_behaves_like 'duplicate.rb'
45
+ end
46
+
47
+ describe 'with file descriptors' do
48
+ before do
49
+ File.open(@temp_in.path, 'r') do |in_io|
50
+ File.open(@temp_out.path, 'w') do |out_io|
51
+ pid = ExecSandbox::Spawn.spawn bin_fixture(:duplicate),
52
+ {:in => in_io, :out => out_io, :err => STDERR}
53
+ @status = ExecSandbox::Wait4.wait4 pid
54
+ end
55
+ end
56
+ end
57
+
58
+ it_behaves_like 'duplicate.rb'
59
+ end
60
+
61
+ describe 'without stdout' do
62
+ before do
63
+ pid = ExecSandbox::Spawn.spawn bin_fixture(:duplicate),
64
+ {:in => @temp_in.path}
65
+ @status = ExecSandbox::Wait4.wait4 pid
66
+ end
67
+
68
+ it 'should crash' do
69
+ @status[:exit_code].should_not == 0
70
+ end
71
+ end
72
+ end
73
+
74
+ describe '#spawn principal' do
75
+ before do
76
+ @temp = Tempfile.new 'exec_sandbox_rspec'
77
+ @temp_path = @temp.path
78
+ @temp.close
79
+ end
80
+ after do
81
+ File.unlink(@temp_path) if File.exist?(@temp_path)
82
+ end
83
+
84
+ describe 'with root credentials' do
85
+ before do
86
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:write_arg),
87
+ @temp_path, "Spawn uid test\n"], {:err => STDERR},
88
+ {:uid => 0, :gid => 0}
89
+ @status = ExecSandbox::Wait4.wait4 pid
90
+ @fstat = File.stat(@temp_path)
91
+ end
92
+
93
+ it 'should not crash' do
94
+ @status[:exit_code].should == 0
95
+ end
96
+
97
+ it 'should have the UID set to root' do
98
+ @fstat.uid.should == 0
99
+ end
100
+ it 'should have the GID set to root' do
101
+ @fstat.gid.should == 0
102
+ end
103
+
104
+ it 'should have the correct output' do
105
+ File.read(@temp_path).should == "Spawn uid test\n"
106
+ end
107
+ end
108
+
109
+ describe 'with non-root credentials' do
110
+ before do
111
+ @temp.unlink
112
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:write_arg),
113
+ @temp_path, "Spawn uid test\n"], {:err => STDERR},
114
+ {:uid => test_uid, :gid => test_gid}
115
+ @status = ExecSandbox::Wait4.wait4 pid
116
+ end
117
+
118
+ it 'should not crash' do
119
+ @status[:exit_code].should == 0
120
+ end
121
+
122
+ it 'should have the UID set to the test user' do
123
+ File.stat(@temp_path).uid.should == test_uid
124
+ end
125
+ it 'should have the GID set to the test group' do
126
+ File.stat(@temp_path).gid.should == test_gid
127
+ end
128
+
129
+ it 'should have the correct output' do
130
+ File.read(@temp_path).should == "Spawn uid test\n"
131
+ end
132
+ end
133
+
134
+ describe 'with non-root credentials and a root-owned redirect file' do
135
+ before do
136
+ File.chmod 0700, @temp_path
137
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:write_arg),
138
+ @temp_path, "Spawn uid test\n"], {},
139
+ {:uid => test_uid, :gid => test_gid}
140
+ @status = ExecSandbox::Wait4.wait4 pid
141
+ end
142
+
143
+ it 'should crash (euid is set correctly)' do
144
+ @status[:exit_code].should_not == 0
145
+ end
146
+
147
+ it 'should not have the correct output' do
148
+ File.read(@temp_path).should_not == "Spawn uid test\n"
149
+ end
150
+ end
151
+
152
+ describe 'with non-root credentials and a root-owned redirect file' do
153
+ before do
154
+ File.chmod 070, @temp_path
155
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:write_arg), @temp_path,
156
+ "Spawn uid test\n"], {},
157
+ {:uid => test_uid, :gid => test_gid}
158
+ @status = ExecSandbox::Wait4.wait4 pid
159
+ end
160
+
161
+ it 'should crash (egid is set correctly)' do
162
+ @status[:exit_code].should_not == 0
163
+ end
164
+
165
+ it 'should not have the correct output' do
166
+ File.read(@temp_path).should_not == "Spawn uid test\n"
167
+ end
168
+ end
169
+
170
+ describe 'with a working directory' do
171
+ before do
172
+ @temp_dir = Dir.mktmpdir 'exec_sandbox_rspec'
173
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:pwd), @temp_path],
174
+ {}, {:dir => @temp_dir}
175
+ @status = ExecSandbox::Wait4.wait4 pid
176
+ end
177
+ after do
178
+ Dir.rmdir @temp_dir
179
+ end
180
+
181
+ it 'should not crash' do
182
+ @status[:exit_code].should == 0
183
+ end
184
+
185
+ it 'should set the working directory' do
186
+ File.read(@temp_path).should == @temp_dir
187
+ end
188
+ end
189
+ end
190
+
191
+ describe '#spawn resource limits' do
192
+ before do
193
+ @temp = Tempfile.new 'exec_sandbox_rspec'
194
+ @temp_path = @temp.path
195
+ @temp.close
196
+ end
197
+ after do
198
+ File.unlink(@temp_path) if File.exist?(@temp_path)
199
+ end
200
+
201
+ describe 'buffer.rb' do
202
+ describe 'without limitations' do
203
+ before do
204
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:buffer), @temp_path,
205
+ (100 * 1024 * 1024).to_s], {:err => STDERR}, {}, {}
206
+ @status = ExecSandbox::Wait4.wait4 pid
207
+ end
208
+
209
+ it 'should not crash' do
210
+ @status[:exit_code].should == 0
211
+ end
212
+
213
+ it 'should output 100 megs' do
214
+ File.stat(@temp_path).size.should == 100 * 1024 * 1024
215
+ end
216
+ end
217
+
218
+ describe 'with memory limitation' do
219
+ before do
220
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:buffer), @temp_path,
221
+ (100 * 1024 * 1024).to_s], {}, {}, {:data => 64 * 1024 * 1024}
222
+ @status = ExecSandbox::Wait4.wait4 pid
223
+ end
224
+
225
+ it 'should crash' do
226
+ @status[:exit_code].should_not == 0
227
+ end
228
+
229
+ it 'should not have a chance to output data' do
230
+ File.stat(@temp_path).size.should == 0
231
+ end
232
+ end
233
+
234
+ describe 'with output limitation' do
235
+ before do
236
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:buffer), @temp_path,
237
+ (100 * 1024 * 1024).to_s], {}, {}, {:file_size => 8 * 1024 * 1024}
238
+ @status = ExecSandbox::Wait4.wait4 pid
239
+ end
240
+
241
+ it 'should crash' do
242
+ @status[:exit_code].should_not == 0
243
+ end
244
+
245
+ it 'should not output more than 16 megs' do
246
+ File.stat(@temp_path).size.should <= 16 * 1024 * 1024
247
+ end
248
+ end
249
+ end
250
+
251
+ describe 'fork.rb' do
252
+ describe 'without limitations' do
253
+ before do
254
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:fork), @temp_path,
255
+ 10.to_s], {:err => STDERR}, {}, {}
256
+ @status = ExecSandbox::Wait4.wait4 pid
257
+ end
258
+
259
+ it 'should not crash' do
260
+ @status[:exit_code].should == 0
261
+ end
262
+
263
+ it 'should output 10 +es' do
264
+ File.stat(@temp_path).size.should == 10
265
+ end
266
+ end
267
+
268
+ describe 'with sub-process limitation' do
269
+ before do
270
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:fork), @temp_path,
271
+ 10.to_s], {}, {}, {:processes => 4}
272
+ @status = ExecSandbox::Wait4.wait4 pid
273
+ end
274
+
275
+ it 'should crash' do
276
+ @status[:exit_code].should_not == 0
277
+ end
278
+
279
+ it 'should output less than 5 +es' do
280
+ File.stat(@temp_path).size.should < 5
281
+ end
282
+ end
283
+ end
284
+
285
+ describe 'churn.rb' do
286
+ describe 'without limitations' do
287
+ before do
288
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:churn), @temp_path,
289
+ 3.to_s], {:err => STDERR}, {}, {}
290
+ @status = ExecSandbox::Wait4.wait4 pid
291
+ end
292
+
293
+ it 'should not crash' do
294
+ @status[:exit_code].should == 0
295
+ end
296
+
297
+ it 'should run for at least 2 seconds' do
298
+ (@status[:user_time] + @status[:system_time]).should > 2
299
+ end
300
+
301
+ it 'should output something' do
302
+ File.stat(@temp_path).size.should > 0
303
+ end
304
+ end
305
+
306
+ describe 'with CPU time limitation' do
307
+ before do
308
+ pid = ExecSandbox::Spawn.spawn [bin_fixture(:churn), @temp_path,
309
+ 10.to_s], {}, {}, {:cpu => 1}
310
+ @status = ExecSandbox::Wait4.wait4 pid
311
+ end
312
+
313
+ it 'should run for at least 0.5 seconds' do
314
+ (@status[:user_time] + @status[:system_time]).should >= 0.5
315
+ end
316
+
317
+ it 'should run for less than 2 seconds' do
318
+ (@status[:user_time] + @status[:system_time]).should < 2
319
+ end
320
+
321
+ it 'should not have a chance to output' do
322
+ File.stat(@temp_path).size.should == 0
323
+ end
324
+ end
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,125 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ExecSandbox::Users do
4
+ let(:test_user) { 'exec_sandbox_rspec' }
5
+ let(:test_group) { Etc.getgrgid(Etc.getpwnam(Etc.getlogin).gid).name }
6
+
7
+ describe '#temp' do
8
+ before { @user_name = ExecSandbox::Users.temp 'exsbx.rspec' }
9
+ after { ExecSandbox::Users.destroy @user_name }
10
+
11
+ it 'should create a user whose name starts with the prefix' do
12
+ @user_name.should match(/^exsbx\.rspec/)
13
+ end
14
+
15
+ it 'should create a user' do
16
+ Etc.getpwnam(@user_name).should_not be_nil
17
+ end
18
+
19
+ it 'should create a group with the same name' do
20
+ Etc.getgrgid(Etc.getpwnam(@user_name).gid).name.should == @user_name
21
+ end
22
+
23
+ it 'should create a home directory for the user' do
24
+ File.exist?(Etc.getpwnam(@user_name).dir).should be_true
25
+ end
26
+ end
27
+
28
+ describe '#create' do
29
+ shared_examples_for 'user creation' do
30
+ it 'should return the UID of a user with the right name' do
31
+ Etc.getpwuid(@uid).name.should == test_user
32
+ end
33
+
34
+ it "should create the new user's home directory" do
35
+ File.exist?(Etc.getpwuid(@uid).dir).should be_true
36
+ end
37
+
38
+ it "should have the new user's name in its home directory" do
39
+ Etc.getpwuid(@uid).dir.should match(test_user)
40
+ end
41
+ end
42
+
43
+ describe 'with no group' do
44
+ before do
45
+ @uid = ExecSandbox::Users.create test_user
46
+ end
47
+
48
+ after do
49
+ ExecSandbox::Users.destroy test_user
50
+ end
51
+
52
+ it_should_behave_like 'user creation'
53
+
54
+ it "should create a group with the user's name" do
55
+ Etc.getgrnam(test_user).should_not be_nil
56
+ end
57
+
58
+ it "should set the new user's GID to the group" do
59
+ Etc.getpwuid(@uid).gid.should == Etc.getgrnam(test_user).gid
60
+ end
61
+ end
62
+
63
+ describe 'with given group' do
64
+ before do
65
+ @uid = ExecSandbox::Users.create test_user, test_group
66
+ end
67
+
68
+ after do
69
+ ExecSandbox::Users.destroy test_user
70
+ end
71
+
72
+ it_should_behave_like 'user creation'
73
+
74
+ it "should not create a group with the user's name" do
75
+ lambda {
76
+ Etc.getgrnam(test_user)
77
+ }.should raise_error(ArgumentError)
78
+ end
79
+
80
+ it "should set the new user's GID to the correct group" do
81
+ Etc.getpwuid(@uid).gid.should == Etc.getgrnam(test_group).gid
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#destroy' do
87
+ describe 'with single-use group' do
88
+ before do
89
+ ExecSandbox::Users.create test_user
90
+ @home_dir = Etc.getpwnam(test_user)
91
+ ExecSandbox::Users.destroy test_user
92
+ end
93
+
94
+ it 'should remove the user' do
95
+ lambda {
96
+ Etc.getpwnam(test_user)
97
+ }.should raise_error(ArgumentError)
98
+ end
99
+
100
+ it "should remove the user's group" do
101
+ lambda {
102
+ Etc.getgrnam(test_user)
103
+ }.should raise_error(ArgumentError)
104
+ end
105
+ end
106
+
107
+ describe 'delete_user with shared group' do
108
+ before do
109
+ ExecSandbox::Users.create test_user, test_group
110
+ @home_dir = Etc.getpwnam(test_user)
111
+ ExecSandbox::Users.destroy test_user
112
+ end
113
+
114
+ it 'should remove the user' do
115
+ lambda {
116
+ Etc.getpwnam(test_user)
117
+ }.should raise_error(ArgumentError)
118
+ end
119
+
120
+ it "should not remove the generic group" do
121
+ Etc.getgrnam(test_group).should_not be_nil
122
+ end
123
+ end
124
+ end
125
+ end