jruby_sandbox 0.1.3-java → 0.1.4-java

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -4,4 +4,4 @@ source "http://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  # this fork has some recent work done for 1.9, like File.foreach
7
- gem 'fakefs', :git => 'git://github.com/nanothief/fakefs.git', :require => 'fakefs/safe'
7
+ gem 'fakefs', :git => 'git://github.com/codeschool/fakefs.git', :require => 'fakefs/safe', :ref => "6ae3212e3dab013b4b5e290d1aceba6466b30dec"
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  GIT
2
- remote: git://github.com/nanothief/fakefs.git
3
- revision: cb63ff262e1ab9dffdda6d20a1b9c90434e97d58
2
+ remote: git://github.com/codeschool/fakefs.git
3
+ revision: 6ae3212e3dab013b4b5e290d1aceba6466b30dec
4
+ ref: 6ae3212e3dab013b4b5e290d1aceba6466b30dec
4
5
  specs:
5
- fakefs (0.3.2)
6
+ fakefs (0.4.0)
6
7
 
7
8
  PATH
8
9
  remote: .
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2006 Ola Bini <ola@ologix.com>
2
+ Copyright (c) 2011 Dray Lacy and Eric Allam
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -17,13 +17,11 @@ import org.jruby.runtime.Block;
17
17
  import org.jruby.runtime.builtin.IRubyObject;
18
18
  import org.jruby.common.IRubyWarnings;
19
19
  import org.jruby.exceptions.RaiseException;
20
- import org.jruby.runtime.DynamicScope;
21
20
 
22
21
 
23
22
  @JRubyClass(name="Sandbox::Full")
24
23
  public class SandboxFull extends RubyObject {
25
24
  private Ruby wrapped;
26
- private DynamicScope currentScope;
27
25
 
28
26
  public SandboxFull(Ruby runtime, RubyClass type) {
29
27
  super(runtime, type);
@@ -47,8 +45,6 @@ public class SandboxFull extends RubyObject {
47
45
 
48
46
  RubyClass cBoxedClass = wrapped.defineClass("BoxedClass", wrapped.getObject(), wrapped.getObject().getAllocator());
49
47
  cBoxedClass.defineAnnotatedMethods(BoxedClass.class);
50
-
51
- currentScope = wrapped.getCurrentContext().getCurrentScope();
52
48
 
53
49
  return this;
54
50
  }
@@ -56,7 +52,7 @@ public class SandboxFull extends RubyObject {
56
52
  @JRubyMethod(required=1)
57
53
  public IRubyObject eval(IRubyObject str) {
58
54
  try {
59
- IRubyObject result = wrapped.evalScriptlet(str.asJavaString(), currentScope);
55
+ IRubyObject result = wrapped.evalScriptlet(str.asJavaString(), wrapped.getCurrentContext().getCurrentScope());
60
56
  return unbox(result);
61
57
  } catch (RaiseException e) {
62
58
  String msg = e.getException().callMethod(wrapped.getCurrentContext(), "message").asJavaString();
@@ -111,29 +107,29 @@ public class SandboxFull extends RubyObject {
111
107
  // superclasses as well.
112
108
  sup = importClassPath(runtimeModule.getSuperClass().getName(), true);
113
109
  }
114
-
110
+
115
111
  RubyClass klass = (RubyClass) sup;
116
112
  if (wrappedModule == wrapped.getObject()) {
117
-
113
+
118
114
  if (link || runtimeModule instanceof RubyClass){ // if this is a ref and not an import
119
115
  wrappedModule = wrapped.defineClass(name, klass, klass.getAllocator());
120
116
  } else {
121
117
  wrappedModule = wrapped.defineModule(name);
122
118
  }
123
-
119
+
124
120
  } else {
125
121
  if (runtimeModule instanceof RubyClass){
126
122
  wrappedModule = wrappedModule.defineClassUnder(name, klass, klass.getAllocator());
127
123
  } else {
128
124
  wrappedModule = wrappedModule.defineModuleUnder(name);
129
125
  }
130
-
126
+
131
127
  }
132
128
  } else {
133
129
  // ...or just resolve it, if it was already known
134
130
  wrappedModule = (RubyModule) wrappedModule.getConstantAt(name);
135
131
  }
136
-
132
+
137
133
  // Check the consistency of the hierarchy
138
134
  if (runtimeModule instanceof RubyClass) {
139
135
  if (!link && !runtimeModule.getSuperClass().getName().equals(wrappedModule.getSuperClass().getName())) {
Binary file
@@ -1,3 +1,3 @@
1
1
  module Sandbox
2
- VERSION = '0.1.3'
2
+ VERSION = '0.1.4'
3
3
  end
data/lib/sandbox.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  require 'sandbox/sandbox'
2
2
  require 'sandbox/version'
3
3
  require 'fakefs/safe'
4
+ require 'timeout'
4
5
 
5
6
  module Sandbox
6
7
  PRELUDE = File.expand_path('../sandbox/prelude.rb', __FILE__).freeze # :nodoc:
7
-
8
- TimeoutError = Class.new(Exception)
9
8
 
10
9
  class << self
11
10
  def new
@@ -20,7 +19,7 @@ module Sandbox
20
19
  class Safe < Full
21
20
  def activate!
22
21
  activate_fakefs
23
-
22
+
24
23
  keep_singleton_methods(:Kernel, KERNEL_S_METHODS)
25
24
  keep_singleton_methods(:Symbol, SYMBOL_S_METHODS)
26
25
  keep_singleton_methods(:String, STRING_S_METHODS)
@@ -34,86 +33,62 @@ module Sandbox
34
33
  keep_methods(:Enumerable, ENUMERABLE_METHODS)
35
34
  keep_methods(:String, STRING_METHODS)
36
35
  end
37
-
36
+
38
37
  def activate_fakefs
39
38
  require 'fileutils'
40
-
39
+
41
40
  # unfortunately, the authors of FakeFS used `extend self` in FileUtils, instead of `module_function`.
42
41
  # I fixed it for them
43
42
  (FakeFS::FileUtils.methods - Module.methods - Kernel.methods).each do |module_method_name|
44
43
  FakeFS::FileUtils.send(:module_function, module_method_name)
45
44
  end
46
-
45
+
47
46
  import FakeFS
48
47
  ref FakeFS::Dir
49
48
  ref FakeFS::File
50
49
  ref FakeFS::FileTest
51
50
  import FakeFS::FileUtils #import FileUtils because it is a module
52
-
53
- # this is basically what FakeFS.activate! does, but we want to do it in the sandbox
54
- # so we have to live with this:
51
+
55
52
  eval <<-RUBY
56
53
  Object.class_eval do
57
54
  remove_const(:Dir)
58
55
  remove_const(:File)
59
56
  remove_const(:FileTest)
60
57
  remove_const(:FileUtils)
61
-
58
+
62
59
  const_set(:Dir, FakeFS::Dir)
63
60
  const_set(:File, FakeFS::File)
64
61
  const_set(:FileUtils, FakeFS::FileUtils)
65
62
  const_set(:FileTest, FakeFS::FileTest)
63
+
64
+ [Dir, File, FileUtils, FileTest].each do |fake_class|
65
+ fake_class.class_eval do
66
+ def self.class_eval
67
+ raise NoMethodError, "class_eval is unavailable"
68
+ end
69
+ def self.instance_eval
70
+ raise NoMethodError, "instance_eval is unavailable"
71
+ end
72
+ end
73
+ end
66
74
  end
67
75
  RUBY
68
-
76
+
69
77
  FakeFS::FileSystem.clear
70
78
  end
71
-
72
- def eval(code, options={})
73
-
74
- if seconds = options[:timeout]
75
- sandbox_timeout(code, seconds) do
76
- super code
77
- end
78
- else
79
- super code
80
- end
81
-
82
- end
83
-
84
- private
85
-
86
- def sandbox_timeout(name, seconds)
87
- val, exc = nil
88
-
89
- thread = Thread.start(name) do
90
- begin
91
- val = yield
92
- rescue Exception => exc
93
- end
94
- end
95
-
96
- thread.join(seconds)
97
-
98
- if thread.alive?
99
- if thread.respond_to? :kill!
100
- thread.kill!
101
- else
102
- thread.kill
79
+
80
+ def eval_with_timeout(code, timeout=10)
81
+ require 'timeout'
82
+
83
+ timeout_code = <<-RUBY
84
+ Timeout.timeout(#{timeout}) do
85
+ #{code}
103
86
  end
104
-
105
- timed_out = true
106
- end
107
-
108
- if timed_out
109
- raise TimeoutError, "#{self.class} timed out"
110
- elsif exc
111
- raise exc
112
- else
113
- val
114
- end
87
+ RUBY
88
+
89
+ eval timeout_code
115
90
  end
116
-
91
+
117
92
  IO_S_METHODS = %w[
118
93
  new
119
94
  foreach
@@ -7,71 +7,107 @@ end
7
7
 
8
8
  describe "Sandbox exploits" do
9
9
  subject { Sandbox.safe }
10
-
10
+
11
11
  before(:each) do
12
12
  subject.activate!
13
13
  end
14
-
14
+
15
+ it "should not allow running system commands through File.class_eval" do
16
+ expect {
17
+ subject.eval 'File.class_eval { `echo Hello` }'
18
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
19
+
20
+ expect {
21
+ subject.eval 'FileUtils.class_eval { `echo Hello` }'
22
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
23
+
24
+ expect {
25
+ subject.eval 'Dir.class_eval { `echo Hello` }'
26
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
27
+
28
+ expect {
29
+ subject.eval 'FileTest.class_eval { `echo Hello` }'
30
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
31
+ end
32
+
33
+ it "should not allow running system commands through File.instance_eval" do
34
+ expect {
35
+ subject.eval 'File.instance_eval { `echo Hello` }'
36
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
37
+
38
+ expect {
39
+ subject.eval 'FileUtils.instance_eval { `echo Hello` }'
40
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
41
+
42
+ expect {
43
+ subject.eval 'Dir.instance_eval { `echo Hello` }'
44
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
45
+
46
+ expect {
47
+ subject.eval 'FileTest.instance_eval { `echo Hello` }'
48
+ }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
49
+ end
50
+
15
51
  it "should not allow running any commands or reading files using IO" do
16
52
  expect {
17
53
  subject.eval 'f=IO.popen("uname"); f.readlines; f.close'
18
54
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
19
-
55
+
20
56
  expect {
21
57
  subject.eval 'IO.binread("/etc/passwd")'
22
58
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
23
-
59
+
24
60
  expect {
25
61
  subject.eval 'IO.read("/etc/passwd")'
26
62
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
27
63
  end
28
-
64
+
29
65
  it "should not pass through methods added to Kernel" do
30
66
  k = subject.eval("Kernel")
31
67
  def k.crack
32
68
  open("/etc/passwd")
33
69
  end
34
-
70
+
35
71
  Kernel.should respond_to(:crack)
36
72
  subject.eval("Kernel.respond_to?(:crack)").should == false
37
73
  end
38
-
74
+
39
75
  it "should not allow calling fork on Kernel, even through eval" do
40
76
  subject.eval("eval('Kernel').respond_to?(:fork)").should == false
41
77
  end
42
-
78
+
43
79
  it "should not get access to outside the box objects by using eval and TOPLEVEL_BINDING" do
44
80
  expect {
45
81
  subject.eval(%{eval('OutsideFoo.bar', TOPLEVEL_BINDING)})
46
82
  }.to raise_error(Sandbox::SandboxException, /NameError/)
47
83
  end
48
-
84
+
49
85
  it "should not get access to the outside eval through a ref'd object" do
50
86
  subject.ref OutsideFoo
51
87
  subject.eval "obj = OutsideFoo.new"
52
88
  subject.eval("(obj.methods.grep /^eval/).empty?").should == true
53
89
  subject.eval("obj.respond_to?(:eval)").should == false
54
90
  end
55
-
91
+
56
92
  it "should not allow file access, even through a ref hack" do
57
93
  unsafe_open = %{File.open('/etc/passwd').read}
58
-
94
+
59
95
  expect {
60
- subject.eval(unsafe_open)
96
+ subject.eval(unsafe_open)
61
97
  }.to raise_error(Sandbox::SandboxException)
62
-
98
+
63
99
  subject.ref OutsideFoo
64
100
  subject.eval "obj = OutsideFoo.new"
65
-
101
+
66
102
  unsafe_open_hack = %{obj.eval "#{unsafe_open}"}
67
-
103
+
68
104
  pending "gotta figure out how to lock down ref'd objects eval" do
69
105
  expect {
70
106
  subject.eval(unsafe_open_hack)
71
107
  }.to raise_error(Sandbox::SandboxException)
72
108
  end
73
109
  end
74
-
110
+
75
111
  it "should have safe globals" do
76
112
  subject.eval('$0').should == '(sandbox)'
77
113
  /(.)(.)(.)/.match("abc")
@@ -81,33 +117,33 @@ describe "Sandbox exploits" do
81
117
  subject.eval("$TEST").should == "TEST"
82
118
  subject.eval("$2").should == "e"
83
119
  end
84
-
120
+
85
121
  it "should not keep Kernel.fork" do
86
122
  expect {
87
123
  subject.eval("Kernel.fork")
88
124
  }.to raise_error(Sandbox::SandboxException)
89
-
125
+
90
126
  expect {
91
127
  subject.eval("fork")
92
128
  }.to raise_error(Sandbox::SandboxException)
93
129
  end
94
-
130
+
95
131
  it "should not allow the sandbox to get back to Kernel through ancestors" do
96
132
  subject.eval('$0.class.ancestors[3].respond_to?(:fork)').should == false
97
133
  end
98
-
134
+
99
135
  it "should not pass through block scope" do
100
136
  1.times do |i|
101
137
  subject.eval('local_variables').should == []
102
138
  end
103
139
  end
104
-
140
+
105
141
  it "should not allow exploits through match data" do
106
142
  subject.eval("begin; /(.+)/.match('FreakyFreaky').instance_eval { open('/etc/passwd') }; rescue NameError; :NameError; end").should == :NameError
107
-
143
+
108
144
  subject.eval("begin;(begin;Regexp.new('(');rescue e;e;end).instance_eval{ open('/etc/passwd') }; rescue NameError; :NameError; end").should == :NameError
109
145
  end
110
-
146
+
111
147
  it "should not be able to access outside box Kernel through exceptions" do
112
148
  exception_code = <<-RUBY
113
149
  begin
@@ -117,7 +153,7 @@ describe "Sandbox exploits" do
117
153
  end
118
154
  RUBY
119
155
  subject.eval(exception_code)
120
-
156
+
121
157
  subject.eval('obj.class.ancestors[4].respond_to?(:fork)').should == false
122
158
  end
123
- end
159
+ end
data/spec/sandbox_spec.rb CHANGED
@@ -55,7 +55,7 @@ describe Sandbox do
55
55
 
56
56
  expect {
57
57
  subject.eval(%{FileUtils.cp('/bar.txt', '/baz.txt')})
58
- }.to_not raise_error(Sandbox::SandboxException, /NoMethodError/)
58
+ }.to_not raise_error(Sandbox::SandboxException, /Sandbox::SandboxException: NoMethodError: /)
59
59
  end
60
60
  end
61
61
 
@@ -73,7 +73,7 @@ describe Sandbox do
73
73
  end
74
74
  end
75
75
 
76
- describe "#eval with timeout" do
76
+ describe "#eval_with_timeout" do
77
77
  subject { Sandbox.safe }
78
78
 
79
79
  context "before it's been activated" do
@@ -83,18 +83,8 @@ describe Sandbox do
83
83
  RUBY
84
84
 
85
85
  expect {
86
- subject.eval(long_code, timeout: 1)
87
- }.to raise_error(Sandbox::TimeoutError)
88
- end
89
-
90
- it "should not raise a timeout error if the code runs in under the passed in time" do
91
- short_code = <<-RUBY
92
- 1+1
93
- RUBY
94
-
95
- expect {
96
- subject.eval(short_code, timeout: 1)
97
- }.to_not raise_error(Sandbox::TimeoutError)
86
+ subject.eval_with_timeout(long_code, 1)
87
+ }.to raise_error(Sandbox::SandboxException, /Timeout/)
98
88
  end
99
89
  end
100
90
 
@@ -107,16 +97,10 @@ describe Sandbox do
107
97
  RUBY
108
98
 
109
99
  expect {
110
- subject.eval(long_code, timeout: 1)
111
- }.to raise_error(Sandbox::TimeoutError)
112
- end
113
-
114
- it "should persist state between evaluations" do
115
- subject.eval('o = Object.new', timeout: 1)
116
-
117
- expect {
118
- subject.eval('o', timeout: 1)
119
- }.to_not raise_error(Sandbox::SandboxException)
100
+ Timeout.timeout(3) do
101
+ subject.eval_with_timeout(long_code, 1)
102
+ end
103
+ }.to raise_error(Sandbox::SandboxException, /Timeout/)
120
104
  end
121
105
  end
122
106
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jruby_sandbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  prerelease:
6
6
  platform: java
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2011-09-29 00:00:00.000000000Z
13
+ date: 2013-11-07 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: fakefs
17
- requirement: &70149996586680 !ruby/object:Gem::Requirement
17
+ requirement: !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,15 @@ dependencies:
22
22
  version: '0'
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70149996586680
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
26
31
  - !ruby/object:Gem::Dependency
27
32
  name: rake
28
- requirement: &70149996586260 !ruby/object:Gem::Requirement
33
+ requirement: !ruby/object:Gem::Requirement
29
34
  none: false
30
35
  requirements:
31
36
  - - ! '>='
@@ -33,10 +38,15 @@ dependencies:
33
38
  version: '0'
34
39
  type: :development
35
40
  prerelease: false
36
- version_requirements: *70149996586260
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
37
47
  - !ruby/object:Gem::Dependency
38
48
  name: rake-compiler
39
- requirement: &70149996585840 !ruby/object:Gem::Requirement
49
+ requirement: !ruby/object:Gem::Requirement
40
50
  none: false
41
51
  requirements:
42
52
  - - ! '>='
@@ -44,10 +54,15 @@ dependencies:
44
54
  version: '0'
45
55
  type: :development
46
56
  prerelease: false
47
- version_requirements: *70149996585840
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
48
63
  - !ruby/object:Gem::Dependency
49
64
  name: rspec
50
- requirement: &70149996585420 !ruby/object:Gem::Requirement
65
+ requirement: !ruby/object:Gem::Requirement
51
66
  none: false
52
67
  requirements:
53
68
  - - ! '>='
@@ -55,10 +70,15 @@ dependencies:
55
70
  version: '0'
56
71
  type: :development
57
72
  prerelease: false
58
- version_requirements: *70149996585420
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
59
79
  - !ruby/object:Gem::Dependency
60
80
  name: yard
61
- requirement: &70149996585000 !ruby/object:Gem::Requirement
81
+ requirement: !ruby/object:Gem::Requirement
62
82
  none: false
63
83
  requirements:
64
84
  - - ! '>='
@@ -66,7 +86,12 @@ dependencies:
66
86
  version: '0'
67
87
  type: :development
68
88
  prerelease: false
69
- version_requirements: *70149996585000
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
70
95
  description: A version of _why's Freaky Freaky Sandbox for JRuby.
71
96
  email:
72
97
  - dray@envylabs.com
@@ -79,6 +104,7 @@ files:
79
104
  - .rvmrc
80
105
  - Gemfile
81
106
  - Gemfile.lock
107
+ - LICENSE
82
108
  - README.md
83
109
  - Rakefile
84
110
  - ext/java/sandbox/BoxedClass.java
@@ -114,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
140
  version: '0'
115
141
  requirements: []
116
142
  rubyforge_project: jruby_sandbox
117
- rubygems_version: 1.8.10
143
+ rubygems_version: 1.8.23
118
144
  signing_key:
119
145
  specification_version: 3
120
146
  summary: Sandbox support for JRuby
@@ -122,3 +148,4 @@ test_files:
122
148
  - spec/exploits_spec.rb
123
149
  - spec/sandbox_spec.rb
124
150
  - spec/support/foo.txt
151
+ has_rdoc: