jruby_sandbox 0.1.3-java → 0.1.4-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.
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: