jruby-safe 0.2.2-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ module Sandbox
2
+ VERSION = "0.2.2"
3
+ end
@@ -0,0 +1,185 @@
1
+ require "rspec"
2
+ require "sandbox"
3
+
4
+ class OutsideFoo
5
+ def self.bar; "bar"; end
6
+ end
7
+
8
+ describe "Sandbox exploits" do
9
+ subject { Sandbox.safe }
10
+
11
+ before(:each) do
12
+ subject.activate!
13
+ end
14
+
15
+ it "should not allow access to the filesystem using backticks" do
16
+ expect {
17
+ subject.eval(%|`cat spec/support/foo.txt`|)
18
+ }.to raise_error(Sandbox::SandboxException)
19
+ end
20
+
21
+ it "should not allow running system commands using system" do
22
+ expect {
23
+ subject.eval(%|system("ls")|)
24
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
25
+ end
26
+
27
+ it "should not allow running system commands through File.class_eval" do
28
+ expect {
29
+ subject.eval(%|File.class_eval { `echo Hello` }|)
30
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
31
+
32
+ expect {
33
+ subject.eval(%|FileUtils.class_eval { `echo Hello` }|)
34
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
35
+
36
+ expect {
37
+ subject.eval(%|Dir.class_eval { `echo Hello` }|)
38
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
39
+
40
+ expect {
41
+ subject.eval(%|FileTest.class_eval { `echo Hello` }|)
42
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
43
+ end
44
+
45
+ it "should not allow running system commands through File.eval" do
46
+ expect {
47
+ subject.eval(%|File.eval "`echo Hello`"|)
48
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
49
+
50
+ expect {
51
+ subject.eval(%|FileUtils.eval "`echo Hello`"|)
52
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
53
+
54
+ expect {
55
+ subject.eval(%|Dir.eval "`echo Hello`"|)
56
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
57
+
58
+ expect {
59
+ subject.eval(%|FileTest.eval "`echo Hello`"|)
60
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
61
+ end
62
+
63
+ it "should not allow running system commands through File.instance_eval" do
64
+ expect {
65
+ subject.eval(%|File.instance_eval { `echo Hello` }|)
66
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
67
+
68
+ expect {
69
+ subject.eval(%|FileUtils.instance_eval { `echo Hello` }|)
70
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
71
+
72
+ expect {
73
+ subject.eval(%|Dir.instance_eval { `echo Hello` }|)
74
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
75
+
76
+ expect {
77
+ subject.eval(%|FileTest.instance_eval { `echo Hello` }|)
78
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
79
+ end
80
+
81
+ it "should not allow running any commands or reading files using IO" do
82
+ expect {
83
+ subject.eval(%|f=IO.popen("uname"); f.readlines; f.close|)
84
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
85
+
86
+ expect {
87
+ subject.eval(%|IO.binread("/etc/passwd")|)
88
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
89
+
90
+ expect {
91
+ subject.eval(%|IO.read("/etc/passwd")|)
92
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
93
+ end
94
+
95
+ it "should not pass through methods added to Kernel" do
96
+ k = subject.eval(%|Kernel|)
97
+ def k.crack
98
+ open("/etc/passwd")
99
+ end
100
+
101
+ Kernel.should respond_to(:crack)
102
+ subject.eval(%|Kernel.respond_to?(:crack)|).should == false
103
+ end
104
+
105
+ it "should not allow calling fork on Kernel, even through eval" do
106
+ subject.eval(%|eval("Kernel").respond_to?(:fork)|).should == false
107
+ end
108
+
109
+ it "should not get access to outside the box objects by using eval and TOPLEVEL_BINDING" do
110
+ expect {
111
+ subject.eval(%{eval("OutsideFoo.bar", TOPLEVEL_BINDING)})
112
+ }.to raise_error(Sandbox::SandboxException, /NameError/)
113
+ end
114
+
115
+ it "should not get access to the outside eval through a ref'd object" do
116
+ subject.ref(OutsideFoo)
117
+ subject.eval(%|obj = OutsideFoo.new|)
118
+ subject.eval(%|(obj.methods.grep /^eval/).empty?|).should == true
119
+ subject.eval(%|obj.respond_to?(:eval)|).should == false
120
+ end
121
+
122
+ it "should not allow file access, even through a ref hack" do
123
+ expect {
124
+ subject.eval(%|File.open("/etc/passwd").read|)
125
+ }.to raise_error(Sandbox::SandboxException)
126
+
127
+ subject.ref(OutsideFoo)
128
+ subject.eval(%|obj = OutsideFoo.new|)
129
+
130
+ pending "gotta figure out how to lock down ref'd objects eval" do
131
+ expect {
132
+ subject.eval(%|obj.eval("File.open(\\"/etc/passwd\\").read")|)
133
+ }.to raise_error(Sandbox::SandboxException)
134
+ end
135
+ end
136
+
137
+ it "should have safe globals" do
138
+ subject.eval(%|$0|).should == "(sandbox)"
139
+ /(.)(.)(.)/.match("abc")
140
+ subject.eval(%|$TEST = "TEST"; $TEST|).should == "TEST"
141
+ subject.eval(%|/(.)(.)(.)/.match("def"); $2|).should == "e"
142
+ $2.should == "b"
143
+ subject.eval(%|$TEST|).should == "TEST"
144
+ subject.eval(%|$2|).should == "e"
145
+ end
146
+
147
+ it "should not keep Kernel.fork" do
148
+ expect {
149
+ subject.eval(%|Kernel.fork|)
150
+ }.to raise_error(Sandbox::SandboxException)
151
+
152
+ expect {
153
+ subject.eval(%|fork|)
154
+ }.to raise_error(Sandbox::SandboxException)
155
+ end
156
+
157
+ it "should not allow the sandbox to get back to Kernel through ancestors" do
158
+ subject.eval(%|$0.class.ancestors[3].respond_to?(:fork)|).should == false
159
+ end
160
+
161
+ it "should not pass through block scope" do
162
+ 1.times do |i|
163
+ subject.eval(%|local_variables|).should == []
164
+ end
165
+ end
166
+
167
+ it "should not allow exploits through match data" do
168
+ subject.eval(%|begin; /(.+)/.match("FreakyFreaky").instance_eval { open("/etc/passwd") }; rescue NameError; :NameError; end|).should == :NameError
169
+
170
+ subject.eval(%|begin;(begin;Regexp.new("(");rescue e;e;end).instance_eval{ open("/etc/passwd") }; rescue NameError; :NameError; end|).should == :NameError
171
+ end
172
+
173
+ it "should not be able to access outside box Kernel through exceptions" do
174
+ exception_code = <<-RUBY
175
+ begin
176
+ raise
177
+ rescue => e
178
+ obj = e
179
+ end
180
+ RUBY
181
+ subject.eval(exception_code)
182
+
183
+ subject.eval(%|obj.class.ancestors[4].respond_to?(:fork)|).should == false
184
+ end
185
+ end
@@ -0,0 +1,319 @@
1
+ require "rspec"
2
+ require "sandbox"
3
+ require "timeout"
4
+
5
+ describe Sandbox do
6
+ after(:each) do
7
+ Object.class_eval { remove_const(:Foo) } if defined?(Foo)
8
+ end
9
+
10
+ describe ".new" do
11
+ subject { Sandbox.new }
12
+
13
+ it { should_not be_nil }
14
+ it { should be_an_instance_of(Sandbox::Full) }
15
+ end
16
+
17
+ describe ".safe" do
18
+
19
+ subject { Sandbox.safe }
20
+
21
+ it { should be_an_instance_of(Sandbox::Safe) }
22
+
23
+ it "should not lock down until calling activate!" do
24
+ subject.eval(%|`echo hello`|).should == "hello\n"
25
+
26
+ subject.activate!
27
+
28
+ expect {
29
+ subject.eval(%|`echo hello`|)
30
+ }.to raise_error(Sandbox::SandboxException)
31
+ end
32
+
33
+ it "should activate FakeFS inside the sandbox (and not allow it to be deactivated)" do
34
+ subject.eval(%|File|).should == ::File
35
+
36
+ subject.activate!
37
+
38
+ foo = File.join(File.dirname(__FILE__), "support", "foo.txt")
39
+
40
+ expect {
41
+ subject.eval(%{File.read("#{foo}")})
42
+ }.to raise_error(Sandbox::SandboxException, /Errno::ENOENT: No such file or directory/)
43
+
44
+ subject.eval(%|File|).should == FakeFS::File
45
+ subject.eval(%|Dir|).should == FakeFS::Dir
46
+ subject.eval(%|FileUtils|).should == FakeFS::FileUtils
47
+ subject.eval(%|FileTest|).should == FakeFS::FileTest
48
+
49
+ subject.eval(%{FakeFS.deactivate!})
50
+
51
+ expect {
52
+ subject.eval(%{File.read("#{foo}")})
53
+ }.to raise_error(Sandbox::SandboxException, /Errno::ENOENT: No such file or directory/)
54
+
55
+ subject.eval(%{File.open("/bar.txt", "w") {|file| file << "bar" }})
56
+
57
+ expect {
58
+ subject.eval(%{FileUtils.cp("/bar.txt", "/baz.txt")})
59
+ }.to_not raise_error(Sandbox::SandboxException, /NoMethodError/)
60
+ end
61
+
62
+ context "eval_with_binding" do
63
+ let(:b) { binding }
64
+
65
+ it "should not lock down until calling activate!" do
66
+ subject.eval(%|`echo hello`|, b).should == "hello\n"
67
+
68
+ subject.activate!
69
+
70
+ expect {
71
+ subject.eval(%|`echo hello`|, b)
72
+ }.to raise_error(Sandbox::SandboxException)
73
+ end
74
+
75
+ it "should activate FakeFS inside the sandbox (and not allow it to be deactivated)" do
76
+ subject.eval(%|File|, b).should == ::File
77
+
78
+ subject.activate!
79
+
80
+ foo = File.join(File.dirname(__FILE__), "support", "foo.txt")
81
+
82
+ expect {
83
+ subject.eval(%{File.read("#{foo}")}, b)
84
+ }.to raise_error(Sandbox::SandboxException, /Errno::ENOENT: No such file or directory/)
85
+
86
+ subject.eval(%|File|, b).should == FakeFS::File
87
+ subject.eval(%|Dir|, b).should == FakeFS::Dir
88
+ subject.eval(%|FileUtils|, b).should == FakeFS::FileUtils
89
+ subject.eval(%|FileTest|, b).should == FakeFS::FileTest
90
+
91
+ subject.eval(%{FakeFS.deactivate!}, b)
92
+
93
+ expect {
94
+ subject.eval(%{File.read("#{foo}")}, b)
95
+ }.to raise_error(Sandbox::SandboxException, /Errno::ENOENT: No such file or directory/)
96
+
97
+ subject.eval(%{File.open("/bar.txt", "w") {|file| file << "bar" }}, b)
98
+
99
+ expect {
100
+ subject.eval(%{FileUtils.cp("/bar.txt", "/baz.txt")}, b)
101
+ }.to_not raise_error(Sandbox::SandboxException, /NoMethodError/)
102
+ end
103
+
104
+ it "can set and use local variable" do
105
+ b["x"] = 4
106
+ subject.eval("x", b).should == 4
107
+ end
108
+ end
109
+ end
110
+
111
+ describe ".current" do
112
+ it "should not return a current sandbox outside a sandbox" do
113
+ Sandbox.current.should be_nil
114
+ end
115
+
116
+ it "should return the current sandbox inside a sandbox" do
117
+ pending do
118
+ sandbox = Sandbox.new
119
+ sandbox.ref(Sandbox)
120
+ sandbox.eval(%|Sandbox.current|).should == sandbox
121
+ end
122
+ end
123
+ end
124
+
125
+ describe "#eval with timeout" do
126
+ subject { Sandbox.safe }
127
+
128
+ context "before it's been activated" do
129
+ it "should protect against long running code" do
130
+ long_code = <<-RUBY
131
+ sleep(5)
132
+ RUBY
133
+
134
+ expect {
135
+ subject.eval(long_code, timeout: 1)
136
+ }.to raise_error(Sandbox::TimeoutError)
137
+ end
138
+
139
+ it "should not raise a timeout error if the code runs in under the passed in time" do
140
+ short_code = <<-RUBY
141
+ 1+1
142
+ RUBY
143
+
144
+ expect {
145
+ subject.eval(short_code, timeout: 1)
146
+ }.to_not raise_error(Sandbox::TimeoutError)
147
+ end
148
+ end
149
+
150
+ context "after it's been activated" do
151
+ before(:each) { subject.activate! }
152
+
153
+ it "should protect against long running code" do
154
+ long_code = <<-RUBY
155
+ while true; end
156
+ RUBY
157
+
158
+ expect {
159
+ subject.eval(long_code, timeout: 1)
160
+ }.to raise_error(Sandbox::TimeoutError)
161
+ end
162
+
163
+ it "should persist state between evaluations" do
164
+ subject.eval(%|o = Object.new|, timeout: 1)
165
+
166
+ expect {
167
+ subject.eval(%|o|, timeout: 1)
168
+ }.to_not raise_error(Sandbox::SandboxException)
169
+ end
170
+ end
171
+ end
172
+
173
+ describe "#eval" do
174
+ subject { Sandbox.new }
175
+
176
+ it "should allow a range of common operations" do
177
+ operations = <<-OPS
178
+ 1 + 1
179
+ "foo".chomp
180
+ "foo"
181
+ OPS
182
+ subject.eval(operations).should == "foo"
183
+ end
184
+
185
+ it "should have an empty ENV" do
186
+ pending do
187
+ subject.eval(%{ENV.to_a}).should be_empty
188
+ end
189
+ end
190
+
191
+ it "should persist state between evaluations" do
192
+ subject.eval(%|o = Object.new|)
193
+ subject.eval(%|o|).should_not be_nil
194
+ end
195
+
196
+ it "should be able to define a new class in the sandbox" do
197
+ result = subject.eval(%|Foo = Struct.new(:foo); struct = Foo.new("baz"); struct.foo|)
198
+ result.should == "baz"
199
+ end
200
+
201
+ it "should be able to use a class across invocations" do
202
+ # Return nil, because the environment doesn't know "Foo"
203
+ subject.eval(%|Foo = Struct.new(:foo); nil|)
204
+ subject.eval(%|struct = Foo.new("baz"); nil|)
205
+ subject.eval(%|struct.foo|).should == "baz"
206
+ end
207
+
208
+ describe "communication between sandbox and environment" do
209
+ it "should be possible to pass data from the box to the environment" do
210
+ Foo = Struct.new(:foo)
211
+ subject.ref(Foo)
212
+ struct = subject.eval(%|struct = Foo.new|)
213
+ subject.eval(%|struct.foo = "baz"|)
214
+ struct.foo.should == "baz"
215
+ end
216
+
217
+ it "should be possible to pass data from the environment to the box" do
218
+ Foo = Struct.new(:foo)
219
+ subject.ref(Foo)
220
+ struct = subject.eval(%|struct = Foo.new|)
221
+ struct.foo = "baz"
222
+ subject.eval(%|struct.foo|).should == "baz"
223
+ end
224
+
225
+ it "should be able to pass large object data from the box to the environment" do
226
+ expect {
227
+ subject.eval %{
228
+ (0..1000).to_a.inject({}) {|h,i| h[i] = "HELLO WORLD"; h }
229
+ }
230
+ }.to_not raise_error(Sandbox::SandboxException)
231
+
232
+ expect {
233
+ subject.eval %{"RUBY"*100}
234
+ }.to_not raise_error(Sandbox::SandboxException)
235
+ end
236
+ end
237
+ end
238
+
239
+ describe "#import" do
240
+ subject { Sandbox.new }
241
+
242
+ it "should be able to call a referenced namespaced module method" do
243
+ Foo = Class.new
244
+ Foo::Bar = Module.new do
245
+ def baz
246
+ "baz"
247
+ end
248
+ module_function :baz
249
+ end
250
+
251
+ subject.import(Foo::Bar)
252
+ subject.eval(%|Foo::Bar.baz|).should == "baz"
253
+ end
254
+
255
+ it "should be able to include a module from the environment" do
256
+ Foo = Module.new do
257
+ def baz
258
+ "baz"
259
+ end
260
+ end
261
+
262
+ subject.import(Foo)
263
+ subject.eval(%|class Bar; include Foo; end; nil|)
264
+ subject.eval(%|Bar.new.baz|).should == "baz"
265
+ end
266
+
267
+ it "should be able to copy instance methods from a module that uses module_function" do
268
+ Foo = Module.new do
269
+ def baz; "baz"; end
270
+
271
+ module_function :baz
272
+ end
273
+
274
+ subject.import Foo
275
+ subject.eval(%|Foo.baz|).should == "baz"
276
+ end
277
+ end
278
+
279
+ describe "#ref" do
280
+ subject { Sandbox.new }
281
+
282
+ it "should be possible to reference a class defined outside the box" do
283
+ Foo = Class.new
284
+ subject.ref(Foo)
285
+ subject.eval(%|Foo.new|).should be_an_instance_of(Foo)
286
+ end
287
+
288
+ it "should be possible to change the class after the ref" do
289
+ Foo = Class.new
290
+ subject.ref(Foo)
291
+ def Foo.foo; "baz"; end
292
+ subject.eval(%|Foo.foo|).should == "baz"
293
+ end
294
+
295
+ it "should be possible to dynamically add a class method after the ref" do
296
+ Foo = Class.new
297
+ subject.ref(Foo)
298
+ Foo.class_eval(%|def Foo.foo; "baz"; end|)
299
+ subject.eval(%|Foo.foo|).should == "baz"
300
+ end
301
+
302
+ it "should be possible to dynamically add a class method after the ref" do
303
+ Foo = Class.new
304
+ subject.ref(Foo)
305
+ Foo.instance_eval(%|def Foo.foo; "baz"; end|)
306
+ subject.eval(%|Foo.foo|).should == "baz"
307
+ end
308
+
309
+ it "should be possible to call a method on the class that receives a block" do
310
+ Foo = Class.new do
311
+ def self.bar
312
+ yield
313
+ end
314
+ end
315
+ subject.ref(Foo)
316
+ subject.eval(%|Foo.bar { "baz" }|).should == "baz"
317
+ end
318
+ end
319
+ end