jruby_sandbox 0.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ lib/sandbox/sandbox.jar
4
+ pkg/*
5
+ tags
6
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # This is an RVM Project .rvmrc file, used to automatically load the ruby
4
+ # development environment upon cd'ing into the directory
5
+
6
+ # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
7
+ environment_id="jruby-1.6.3@jruby_sandbox"
8
+
9
+ #
10
+ # Uncomment following line if you want options to be set only for given project.
11
+ #
12
+ PROJECT_JRUBY_OPTS=( --1.9 )
13
+
14
+ #
15
+ # First we attempt to load the desired environment directly from the environment
16
+ # file. This is very fast and efficient compared to running through the entire
17
+ # CLI and selector. If you want feedback on which environment was used then
18
+ # insert the word 'use' after --create as this triggers verbose mode.
19
+ #
20
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
21
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
22
+ then
23
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
24
+
25
+ if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
26
+ then
27
+ . "${rvm_path:-$HOME/.rvm}/hooks/after_use"
28
+ fi
29
+ else
30
+ # If the environment file has not yet been created, use the RVM CLI to select.
31
+ if ! rvm --create "$environment_id"
32
+ then
33
+ echo "Failed to create RVM environment '${environment_id}'."
34
+ exit 1
35
+ fi
36
+ fi
37
+
38
+ #
39
+ # If you use an RVM gemset file to install a list of gems (*.gems), you can have
40
+ # it be automatically loaded. Uncomment the following and adjust the filename if
41
+ # necessary.
42
+ #
43
+ # filename=".gems"
44
+ # if [[ -s "$filename" ]] ; then
45
+ # rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
46
+ # fi
47
+
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in jruby_sandbox.gemspec
4
+ gemspec
5
+
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'
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ GIT
2
+ remote: git://github.com/nanothief/fakefs.git
3
+ revision: cb63ff262e1ab9dffdda6d20a1b9c90434e97d58
4
+ specs:
5
+ fakefs (0.3.2)
6
+
7
+ PATH
8
+ remote: .
9
+ specs:
10
+ jruby_sandbox (0.1.0-java)
11
+ fakefs
12
+
13
+ GEM
14
+ remote: http://rubygems.org/
15
+ specs:
16
+ diff-lcs (1.1.2)
17
+ rake (0.9.2)
18
+ rake-compiler (0.7.9)
19
+ rake
20
+ rspec (2.6.0)
21
+ rspec-core (~> 2.6.0)
22
+ rspec-expectations (~> 2.6.0)
23
+ rspec-mocks (~> 2.6.0)
24
+ rspec-core (2.6.4)
25
+ rspec-expectations (2.6.0)
26
+ diff-lcs (~> 1.1.2)
27
+ rspec-mocks (2.6.0)
28
+ yard (0.7.2)
29
+
30
+ PLATFORMS
31
+ java
32
+
33
+ DEPENDENCIES
34
+ fakefs!
35
+ jruby_sandbox!
36
+ rake
37
+ rake-compiler
38
+ rspec
39
+ yard
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ JRuby Sandbox
2
+ =============
3
+
4
+ The JRuby sandbox is a reimplementation of _why's freaky freaky sandbox
5
+ in JRuby, and is heavily based on [javasand][1] by Ola Bini, but updated
6
+ for JRuby 1.6.
7
+
8
+ ## Prerequisites
9
+
10
+ This gem requires JRuby 1.6. As of the time of this writing, it is known to
11
+ work with the latest stable version of JRuby, 1.6.3. You can install it via
12
+ RVM with the following command:
13
+
14
+ rvm install jruby-1.6.3
15
+
16
+ ## Building
17
+
18
+ To build the JRuby extension, run `rake compile`. This will build the
19
+ `lib/sandbox/sandbox.jar` file, which `lib/sandbox.rb` loads.
20
+
21
+ ## Basic Usage
22
+
23
+ Sandbox gives you a self-contained JRuby interpreter in which to eval
24
+ code without polluting the host environment.
25
+
26
+ >> require "sandbox"
27
+ => true
28
+ >> sand = Sandbox::Full.new
29
+ => #<Sandbox::Full:0x46377e2a>
30
+ >> sand.eval("x = 1 + 2")
31
+ => 3
32
+ >> sand.eval("x")
33
+ => 3
34
+ >> x
35
+ NameError: undefined local variable or method `x' for #<Object:0x11cdc190>
36
+
37
+ There's also `Sandbox::Full#require`, which lets you invoke
38
+ `Kernel#require` directly for the sandbox, so you can load any trusted
39
+ core libraries. Note that this is a direct binding to `Kernel#require`,
40
+ so it will only load ruby stdlib libraries (i.e. no rubygems support
41
+ yet).
42
+
43
+ ## Sandbox::Safe usage
44
+
45
+ Sandbox::Safe exposes an `#activate!` method which will lock down the sandbox, removing unsafe methods. Before calling `#activate!`, Sandbox::Safe is the same as Sandbox::Full.
46
+
47
+ >> require 'sandbox'
48
+ => true
49
+ >> sand = Sandbox.safe
50
+ => #<Sandbox::Safe:0x17072b90>
51
+ >> sand.eval %{`echo HELLO`}
52
+ => "HELLO\n"
53
+ >> sand.activate!
54
+ >> sand.eval %{`echo HELLO`}
55
+ Sandbox::SandboxException: NoMethodError: undefined method ``' for main:Object
56
+
57
+ Sandbox::Safe works by whitelisting methods to keep, and removing the rest. Checkout sandbox.rb for which methods are kept.
58
+
59
+ ## Known Issues / TODOs
60
+
61
+ * It would be a good idea to integrate something like FakeFS to stub
62
+ out the filesystem in the sandbox.
63
+ * There is currently no timeout support, so it's possible for a
64
+ sandbox to loop indefinitely and block the host interpreter.
65
+
66
+ [1]: http://ola-bini.blogspot.com/2006/12/freaky-freaky-sandbox-has-come-to-jruby.html
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/javaextensiontask'
5
+
6
+ Rake::JavaExtensionTask.new('sandbox') do |ext|
7
+ jruby_home = RbConfig::CONFIG['prefix']
8
+ ext.ext_dir = 'ext/java'
9
+ ext.lib_dir = 'lib/sandbox'
10
+ jars = ["#{jruby_home}/lib/jruby.jar"] + FileList['lib/*.jar']
11
+ ext.classpath = jars.map { |x| File.expand_path(x) }.join(':')
12
+ end
13
+
14
+ require 'rspec/core/rake_task'
15
+
16
+ RSpec::Core::RakeTask.new(:spec) do |t|
17
+ t.pattern = 'spec/**/*_spec.rb'
18
+ t.rspec_opts = ['--backtrace']
19
+ end
20
+
21
+ # Make sure the jar is up to date before running specs
22
+ task :spec => :compile
23
+
24
+ task :default => :spec
@@ -0,0 +1,25 @@
1
+ package sandbox;
2
+
3
+ import org.jruby.anno.JRubyClass;
4
+ import org.jruby.anno.JRubyMethod;
5
+ import org.jruby.runtime.builtin.IRubyObject;
6
+ import org.jruby.runtime.Block;
7
+
8
+ @JRubyClass(name="BoxedClass")
9
+ public class BoxedClass {
10
+ @JRubyMethod(module=true, rest=true)
11
+ public static IRubyObject method_missing(IRubyObject recv, IRubyObject[] args, Block block) {
12
+ IRubyObject[] args2 = new IRubyObject[args.length - 1];
13
+ System.arraycopy(args, 1, args2, 0, args2.length);
14
+ String name = args[0].toString();
15
+
16
+ SandboxFull box = (SandboxFull) SandboxFull.getLinkedBox(recv);
17
+ return box.runMethod(recv, name, args2, block);
18
+ }
19
+
20
+ @JRubyMethod(name="new", meta=true, rest=true)
21
+ public static IRubyObject _new(IRubyObject recv, IRubyObject[] args, Block block) {
22
+ SandboxFull box = (SandboxFull) SandboxFull.getLinkedBox(recv);
23
+ return box.runMethod(recv, "new", args, block);
24
+ }
25
+ }
@@ -0,0 +1,308 @@
1
+ package sandbox;
2
+
3
+ import java.util.Collection;
4
+ import java.util.Map;
5
+
6
+ import org.jruby.Ruby;
7
+ import org.jruby.RubyClass;
8
+ import org.jruby.RubyInstanceConfig;
9
+ import org.jruby.RubyKernel;
10
+ import org.jruby.RubyModule;
11
+ import org.jruby.RubyObject;
12
+ import org.jruby.CompatVersion;
13
+ import org.jruby.anno.JRubyClass;
14
+ import org.jruby.anno.JRubyMethod;
15
+ import org.jruby.internal.runtime.methods.DynamicMethod;
16
+ import org.jruby.runtime.Block;
17
+ import org.jruby.runtime.builtin.IRubyObject;
18
+ import org.jruby.common.IRubyWarnings;
19
+ import org.jruby.exceptions.RaiseException;
20
+
21
+
22
+ @JRubyClass(name="Sandbox::Full")
23
+ public class SandboxFull extends RubyObject {
24
+ private Ruby wrapped;
25
+
26
+ public SandboxFull(Ruby runtime, RubyClass type) {
27
+ super(runtime, type);
28
+ reload();
29
+ }
30
+
31
+ @JRubyMethod
32
+ public IRubyObject reload() {
33
+ RubyInstanceConfig cfg = new RubyInstanceConfig();
34
+ cfg.setObjectSpaceEnabled(getRuntime().getInstanceConfig().isObjectSpaceEnabled());
35
+ cfg.setInput(getRuntime().getInstanceConfig().getInput());
36
+ cfg.setOutput(getRuntime().getInstanceConfig().getOutput());
37
+ cfg.setError(getRuntime().getInstanceConfig().getError());
38
+ cfg.setCompatVersion(CompatVersion.RUBY1_9);
39
+ cfg.setScriptFileName("(sandbox)");
40
+
41
+ SandboxProfile profile = new SandboxProfile(this);
42
+ cfg.setProfile(profile);
43
+
44
+ wrapped = Ruby.newInstance(cfg);
45
+
46
+ RubyClass cBoxedClass = wrapped.defineClass("BoxedClass", wrapped.getObject(), wrapped.getObject().getAllocator());
47
+ cBoxedClass.defineAnnotatedMethods(BoxedClass.class);
48
+
49
+ return this;
50
+ }
51
+
52
+ @JRubyMethod(required=1)
53
+ public IRubyObject eval(IRubyObject str) {
54
+ try {
55
+ IRubyObject result = wrapped.evalScriptlet(str.asJavaString(), wrapped.getCurrentContext().getCurrentScope());
56
+ return unbox(result);
57
+ } catch (RaiseException e) {
58
+ String msg = e.getException().callMethod(wrapped.getCurrentContext(), "message").asJavaString();
59
+ String path = e.getException().type().getName();
60
+ RubyClass eSandboxException = (RubyClass) getRuntime().getClassFromPath("Sandbox::SandboxException");
61
+ throw new RaiseException(getRuntime(), eSandboxException, path + ": " + msg, false);
62
+ } catch (Exception e) {
63
+ e.printStackTrace();
64
+ getRuntime().getWarnings().warn(IRubyWarnings.ID.MISCELLANEOUS, "NativeException: " + e);
65
+ return getRuntime().getNil();
66
+ }
67
+ }
68
+
69
+ @JRubyMethod(name="import", required=1)
70
+ public IRubyObject _import(IRubyObject klass) {
71
+ if (!(klass instanceof RubyModule)) {
72
+ throw getRuntime().newTypeError(klass, getRuntime().getClass("Module"));
73
+ }
74
+ String name = ((RubyModule) klass).getName();
75
+ importClassPath(name, false);
76
+ return getRuntime().getNil();
77
+ }
78
+
79
+ @JRubyMethod(required=1)
80
+ public IRubyObject ref(IRubyObject klass) {
81
+ if (!(klass instanceof RubyModule)) {
82
+ throw getRuntime().newTypeError(klass, getRuntime().getClass("Module"));
83
+ }
84
+ String name = ((RubyModule) klass).getName();
85
+ importClassPath(name, true);
86
+ return getRuntime().getNil();
87
+ }
88
+
89
+ private RubyModule importClassPath(String path, final boolean link) {
90
+ RubyModule runtimeModule = getRuntime().getObject();
91
+ RubyModule wrappedModule = wrapped.getObject();
92
+
93
+ if (path.startsWith("#")) {
94
+ throw getRuntime().newArgumentError("can't import anonymous class " + path);
95
+ }
96
+
97
+ for (String name : path.split("::")) {
98
+ runtimeModule = (RubyModule) runtimeModule.getConstantAt(name);
99
+ // Create the module when it did not exist yet...
100
+ if (wrappedModule.const_defined_p(wrapped.getCurrentContext(), wrapped.newString(name)).isFalse()) {
101
+ // The BoxedClass takes the place of Object as top of the inheritance
102
+ // hierarchy. As a result, we can intercept all new instances that are
103
+ // created and all method_missing calls.
104
+ RubyModule sup = wrapped.getClass("BoxedClass");
105
+ if (!link && runtimeModule instanceof RubyClass) {
106
+ // If we're importing a class, recursively import all of its
107
+ // superclasses as well.
108
+ sup = importClassPath(runtimeModule.getSuperClass().getName(), true);
109
+ }
110
+
111
+ RubyClass klass = (RubyClass) sup;
112
+ if (wrappedModule == wrapped.getObject()) {
113
+
114
+ if (link || runtimeModule instanceof RubyClass){ // if this is a ref and not an import
115
+ wrappedModule = wrapped.defineClass(name, klass, klass.getAllocator());
116
+ } else {
117
+ wrappedModule = wrapped.defineModule(name);
118
+ }
119
+
120
+ } else {
121
+ if (runtimeModule instanceof RubyClass){
122
+ wrappedModule = wrappedModule.defineClassUnder(name, klass, klass.getAllocator());
123
+ } else {
124
+ wrappedModule = wrappedModule.defineModuleUnder(name);
125
+ }
126
+
127
+ }
128
+ } else {
129
+ // ...or just resolve it, if it was already known
130
+ wrappedModule = (RubyModule) wrappedModule.getConstantAt(name);
131
+ }
132
+
133
+ // Check the consistency of the hierarchy
134
+ if (runtimeModule instanceof RubyClass) {
135
+ if (!link && !runtimeModule.getSuperClass().getName().equals(wrappedModule.getSuperClass().getName())) {
136
+ throw getRuntime().newTypeError("superclass mismatch for class " + runtimeModule.getSuperClass().getName());
137
+ }
138
+ }
139
+
140
+ if (link || runtimeModule instanceof RubyClass) {
141
+ linkObject(runtimeModule, wrappedModule);
142
+ } else {
143
+ copyMethods(runtimeModule, wrappedModule);
144
+ }
145
+ }
146
+
147
+ return runtimeModule;
148
+ }
149
+
150
+ private void copyMethods(RubyModule from, RubyModule to) {
151
+ to.getMethodsForWrite().putAll(from.getMethods());
152
+ to.getSingletonClass().getMethodsForWrite().putAll(from.getSingletonClass().getMethods());
153
+ }
154
+
155
+ @JRubyMethod(required=2)
156
+ public IRubyObject keep_methods(IRubyObject className, IRubyObject methods) {
157
+ RubyModule module = wrapped.getModule(className.asJavaString());
158
+ if (module != null) {
159
+ keepMethods(module, methods.convertToArray());
160
+ }
161
+ return methods;
162
+ }
163
+
164
+ @JRubyMethod(required=2)
165
+ public IRubyObject keep_singleton_methods(IRubyObject className, IRubyObject methods) {
166
+ RubyModule module = wrapped.getModule(className.asJavaString()).getSingletonClass();
167
+ if (module != null) {
168
+ keepMethods(module, methods.convertToArray());
169
+ }
170
+ return methods;
171
+ }
172
+
173
+ private void keepMethods(RubyModule module, Collection retain) {
174
+ for (Map.Entry<String, DynamicMethod> methodEntry : module.getMethods().entrySet()) {
175
+ String methodName = methodEntry.getKey();
176
+ if (!retain.contains(methodName)) {
177
+ removeMethod(module, methodName);
178
+ }
179
+ }
180
+ }
181
+
182
+ @JRubyMethod(required=2)
183
+ public IRubyObject remove_method(IRubyObject className, IRubyObject methodName) {
184
+ RubyModule module = wrapped.getModule(className.asJavaString());
185
+ if (module != null) {
186
+ removeMethod(module, methodName.asJavaString());
187
+ }
188
+ return getRuntime().getNil();
189
+ }
190
+
191
+ @JRubyMethod(required=2)
192
+ public IRubyObject remove_singleton_method(IRubyObject className, IRubyObject methodName) {
193
+ RubyModule module = wrapped.getModule(className.asJavaString()).getSingletonClass();
194
+ if (module != null) {
195
+ removeMethod(module, methodName.asJavaString());
196
+ }
197
+ return getRuntime().getNil();
198
+ }
199
+
200
+ private void removeMethod(RubyModule module, String methodName) {
201
+ // System.err.println("removing method " + methodName + " from " + module.inspect().asJavaString());
202
+ module.removeMethod(wrapped.getCurrentContext(), methodName);
203
+ }
204
+
205
+ @JRubyMethod(required=1)
206
+ public IRubyObject load(IRubyObject str) {
207
+ try {
208
+ wrapped.getLoadService().load(str.asJavaString(), true);
209
+ return getRuntime().getTrue();
210
+ } catch (RaiseException e) {
211
+ e.printStackTrace();
212
+ return getRuntime().getFalse();
213
+ }
214
+ }
215
+
216
+ @JRubyMethod(required=1)
217
+ public IRubyObject require(IRubyObject str) {
218
+ try {
219
+ IRubyObject result = RubyKernel.require(wrapped.getKernel(), wrapped.newString(str.asJavaString()), Block.NULL_BLOCK);
220
+ return unbox(result);
221
+ } catch (RaiseException e) {
222
+ e.printStackTrace();
223
+ return getRuntime().getFalse();
224
+ }
225
+ }
226
+
227
+ private IRubyObject unbox(IRubyObject obj) {
228
+ return box(obj);
229
+ }
230
+
231
+ private IRubyObject rebox(IRubyObject obj) {
232
+ return box(obj);
233
+ }
234
+
235
+ private IRubyObject box(IRubyObject obj) {
236
+ if (obj.isImmediate()) {
237
+ return cross(obj);
238
+ } else {
239
+ // If this object already existed and was returned from the wrapped
240
+ // runtime on an earlier occasion, it will already contain a link to its
241
+ // brother in the regular runtime and we can safely return that link.
242
+ IRubyObject link = getLinkedObject(obj);
243
+ if (!link.isNil()) {
244
+ IRubyObject box = getLinkedBox(obj);
245
+ if (box == this) return link;
246
+ }
247
+
248
+ // Is the class already known on both sides of the fence?
249
+ IRubyObject klass = constFind(obj.getMetaClass().getRealClass().getName());
250
+ link = getRuntime().getNil();
251
+ if (!klass.isNil()) {
252
+ link = getLinkedObject(klass);
253
+ }
254
+
255
+ if (link.isNil()) {
256
+ return cross(obj);
257
+ } else {
258
+ IRubyObject v = ((RubyClass)klass).allocate();
259
+ linkObject(obj, v);
260
+ return v;
261
+ }
262
+ }
263
+ }
264
+
265
+ private IRubyObject cross(IRubyObject obj) {
266
+ IRubyObject dumped = wrapped.getModule("Marshal").callMethod(wrapped.getCurrentContext(), "dump", obj);
267
+ return getRuntime().getModule("Marshal").callMethod(getRuntime().getCurrentContext(), "load", dumped);
268
+ }
269
+
270
+ protected static IRubyObject getLinkedObject(IRubyObject arg) {
271
+ IRubyObject object = arg.getRuntime().getNil();
272
+ if (arg.getInstanceVariables().getInstanceVariable("__link__") != null) {
273
+ object = (IRubyObject) arg.getInstanceVariables().fastGetInstanceVariable("__link__");
274
+ }
275
+ return object;
276
+ }
277
+
278
+ protected static IRubyObject getLinkedBox(IRubyObject arg) {
279
+ IRubyObject object = arg.getRuntime().getNil();
280
+ if (arg.getInstanceVariables().getInstanceVariable("__box__") != null) {
281
+ object = (IRubyObject) arg.getInstanceVariables().fastGetInstanceVariable("__box__");
282
+ }
283
+ return object;
284
+ }
285
+
286
+ private void linkObject(IRubyObject runtimeObject, IRubyObject wrappedObject) {
287
+ wrappedObject.getInstanceVariables().setInstanceVariable("__link__", runtimeObject);
288
+ wrappedObject.getInstanceVariables().setInstanceVariable("__box__", this);
289
+ }
290
+
291
+ private IRubyObject constFind(String path) {
292
+ try {
293
+ return wrapped.getClassFromPath(path);
294
+ } catch (Exception e) {
295
+ return wrapped.getNil();
296
+ }
297
+ }
298
+
299
+ protected IRubyObject runMethod(IRubyObject recv, String name, IRubyObject[] args, Block block) {
300
+ IRubyObject[] args2 = new IRubyObject[args.length];
301
+ for (int i = 0; i < args.length; i++) {
302
+ args2[i] = unbox(args[i]);
303
+ }
304
+ IRubyObject recv2 = unbox(recv);
305
+ IRubyObject result = recv2.callMethod(getRuntime().getCurrentContext(), name, args2, block);
306
+ return rebox(result);
307
+ }
308
+ }
@@ -0,0 +1,16 @@
1
+ package sandbox;
2
+
3
+ import org.jruby.Profile;
4
+ import org.jruby.anno.JRubyMethod;
5
+ import org.jruby.runtime.builtin.IRubyObject;
6
+
7
+ public class SandboxModule {
8
+ @JRubyMethod(name="current", meta=true)
9
+ public static IRubyObject s_current(IRubyObject recv) {
10
+ Profile prof = recv.getRuntime().getProfile();
11
+ if (prof instanceof SandboxProfile) {
12
+ return ((SandboxProfile) prof).getSandbox();
13
+ }
14
+ return recv.getRuntime().getNil();
15
+ }
16
+ }
@@ -0,0 +1,22 @@
1
+ package sandbox;
2
+
3
+ import org.jruby.Profile;
4
+ import org.jruby.runtime.builtin.IRubyObject;
5
+
6
+ public class SandboxProfile implements Profile {
7
+ private IRubyObject sandbox;
8
+
9
+ public SandboxProfile(IRubyObject sandbox) {
10
+ this.sandbox = sandbox;
11
+ }
12
+
13
+ public IRubyObject getSandbox() {
14
+ return sandbox;
15
+ }
16
+
17
+ public boolean allowBuiltin(String name) { return true; }
18
+ public boolean allowClass(String name) { return true; }
19
+ public boolean allowModule(String name) { return true; }
20
+ public boolean allowLoad(String name) { return true; }
21
+ public boolean allowRequire(String name) { return true; }
22
+ }
@@ -0,0 +1,33 @@
1
+ package sandbox;
2
+
3
+ import java.io.IOException;
4
+
5
+ import org.jruby.Ruby;
6
+ import org.jruby.RubyClass;
7
+ import org.jruby.RubyModule;
8
+ import org.jruby.runtime.ObjectAllocator;
9
+ import org.jruby.runtime.builtin.IRubyObject;
10
+ import org.jruby.runtime.load.BasicLibraryService;
11
+
12
+ public class SandboxService implements BasicLibraryService {
13
+ public boolean basicLoad(Ruby runtime) throws IOException {
14
+ init(runtime);
15
+ return true;
16
+ }
17
+
18
+ private void init(Ruby runtime) {
19
+ RubyModule mSandbox = runtime.defineModule("Sandbox");
20
+ mSandbox.defineAnnotatedMethods(SandboxModule.class);
21
+
22
+ RubyClass cSandboxFull = mSandbox.defineClassUnder("Full", runtime.getObject(), FULL_ALLOCATOR);
23
+ cSandboxFull.defineAnnotatedMethods(SandboxFull.class);
24
+
25
+ RubyClass cSandboxException = mSandbox.defineClassUnder("SandboxException", runtime.getException(), runtime.getException().getAllocator());
26
+ }
27
+
28
+ protected static final ObjectAllocator FULL_ALLOCATOR = new ObjectAllocator() {
29
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
30
+ return new SandboxFull(runtime, klass);
31
+ }
32
+ };
33
+ }
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'sandbox/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'jruby_sandbox'
7
+ s.version = Sandbox::VERSION
8
+ s.platform = 'java'
9
+ s.authors = ['Dray Lacy', 'Eric Allam']
10
+ s.email = ['dray@envylabs.com', 'eric@envylabs.com']
11
+ s.homepage = 'http://github.com/omghax/jruby_sandbox'
12
+ s.summary = 'Sandbox support for JRuby'
13
+ s.description = "A version of _why's Freaky Freaky Sandbox for JRuby."
14
+
15
+ s.rubyforge_project = 'jruby_sandbox'
16
+
17
+ s.files = `git ls-files`.split("\n") + ['lib/sandbox/sandbox.jar']
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+
22
+ s.add_dependency 'fakefs'
23
+ s.add_development_dependency 'rake'
24
+ s.add_development_dependency 'rake-compiler'
25
+ s.add_development_dependency 'rspec'
26
+ s.add_development_dependency 'yard'
27
+ end
@@ -0,0 +1,19 @@
1
+ # Alternate "safer" versions of Ruby methods. Mostly non-blocking.
2
+ [Fixnum, Bignum, Float].each do |klass|
3
+ klass.class_eval do
4
+ # A very weak version of pow, it doesn't work on Floats, but it's gonna
5
+ # fill the most common uses for now.
6
+ def **(x)
7
+ case x
8
+ when 0; 1
9
+ when 1; self
10
+ else
11
+ y = 1
12
+ while 0 <= (x -= 1) do
13
+ y *= self
14
+ end
15
+ y
16
+ end
17
+ end
18
+ end
19
+ end
Binary file
@@ -0,0 +1,3 @@
1
+ module Sandbox
2
+ VERSION = '0.1.0'
3
+ end
data/lib/sandbox.rb ADDED
@@ -0,0 +1,333 @@
1
+ require 'sandbox/sandbox'
2
+ require 'sandbox/version'
3
+ require 'fakefs/safe'
4
+
5
+ module Sandbox
6
+ PRELUDE = File.expand_path('../sandbox/prelude.rb', __FILE__).freeze # :nodoc:
7
+
8
+ class << self
9
+ def new
10
+ Full.new
11
+ end
12
+
13
+ def safe
14
+ Safe.new
15
+ end
16
+ end
17
+
18
+ class Safe < Full
19
+ def activate!
20
+ activate_fakefs
21
+
22
+ keep_singleton_methods(:Kernel, KERNEL_S_METHODS)
23
+ keep_singleton_methods(:Symbol, SYMBOL_S_METHODS)
24
+ keep_singleton_methods(:String, STRING_S_METHODS)
25
+
26
+ keep_methods(:Kernel, KERNEL_METHODS)
27
+ keep_methods(:NilClass, NILCLASS_METHODS)
28
+ keep_methods(:Symbol, SYMBOL_METHODS)
29
+ keep_methods(:TrueClass, TRUECLASS_METHODS)
30
+ keep_methods(:FalseClass, FALSECLASS_METHODS)
31
+ keep_methods(:Enumerable, ENUMERABLE_METHODS)
32
+ keep_methods(:String, STRING_METHODS)
33
+ end
34
+
35
+ def activate_fakefs
36
+ require 'fileutils'
37
+
38
+ # unfortunately, the authors of FakeFS used `extend self` in FileUtils, instead of `module_function`.
39
+ # I fixed it for them
40
+ (FakeFS::FileUtils.methods - Module.methods - Kernel.methods).each do |module_method_name|
41
+ FakeFS::FileUtils.send(:module_function, module_method_name)
42
+ end
43
+
44
+ import FakeFS
45
+ ref FakeFS::Dir
46
+ ref FakeFS::File
47
+ ref FakeFS::FileTest
48
+ import FakeFS::FileUtils #import FileUtils because it is a module
49
+
50
+ eval <<-RUBY
51
+ Object.class_eval do
52
+ remove_const(:Dir)
53
+ remove_const(:File)
54
+ remove_const(:FileTest)
55
+ remove_const(:FileUtils)
56
+
57
+ const_set(:Dir, FakeFS::Dir)
58
+ const_set(:File, FakeFS::File)
59
+ const_set(:FileUtils, FakeFS::FileUtils)
60
+ const_set(:FileTest, FakeFS::FileTest)
61
+ end
62
+ RUBY
63
+
64
+ FakeFS::FileSystem.clear
65
+ end
66
+
67
+ KERNEL_S_METHODS = %w[
68
+ Array
69
+ binding
70
+ block_given?
71
+ catch
72
+ chomp
73
+ chomp!
74
+ chop
75
+ chop!
76
+ eval
77
+ fail
78
+ Float
79
+ format
80
+ global_variables
81
+ gsub
82
+ gsub!
83
+ Integer
84
+ iterator?
85
+ lambda
86
+ local_variables
87
+ loop
88
+ method_missing
89
+ proc
90
+ raise
91
+ scan
92
+ split
93
+ sprintf
94
+ String
95
+ sub
96
+ sub!
97
+ throw
98
+ ].freeze
99
+
100
+ SYMBOL_S_METHODS = %w[
101
+ all_symbols
102
+ ].freeze
103
+
104
+ STRING_S_METHODS = %w[
105
+ new
106
+ ].freeze
107
+
108
+ KERNEL_METHODS = %w[
109
+ ==
110
+ ===
111
+ =~
112
+ Array
113
+ binding
114
+ block_given?
115
+ catch
116
+ chomp
117
+ chomp!
118
+ chop
119
+ chop!
120
+ class
121
+ clone
122
+ dup
123
+ eql?
124
+ equal?
125
+ eval
126
+ fail
127
+ Float
128
+ format
129
+ freeze
130
+ frozen?
131
+ global_variables
132
+ gsub
133
+ gsub!
134
+ hash
135
+ id
136
+ initialize_copy
137
+ inspect
138
+ instance_eval
139
+ instance_of?
140
+ instance_variables
141
+ instance_variable_get
142
+ instance_variable_set
143
+ instance_variable_defined?
144
+ Integer
145
+ is_a?
146
+ iterator?
147
+ kind_of?
148
+ lambda
149
+ local_variables
150
+ loop
151
+ methods
152
+ method_missing
153
+ nil?
154
+ private_methods
155
+ print
156
+ proc
157
+ protected_methods
158
+ public_methods
159
+ raise
160
+ remove_instance_variable
161
+ respond_to?
162
+ respond_to_missing?
163
+ scan
164
+ send
165
+ singleton_methods
166
+ singleton_method_added
167
+ singleton_method_removed
168
+ singleton_method_undefined
169
+ split
170
+ sprintf
171
+ String
172
+ sub
173
+ sub!
174
+ taint
175
+ tainted?
176
+ throw
177
+ to_a
178
+ to_s
179
+ type
180
+ untaint
181
+ __send__
182
+ ].freeze
183
+
184
+ NILCLASS_METHODS = %w[
185
+ &
186
+ inspect
187
+ nil?
188
+ to_a
189
+ to_f
190
+ to_i
191
+ to_s
192
+ ^
193
+ |
194
+ ].freeze
195
+
196
+ SYMBOL_METHODS = %w[
197
+ ===
198
+ id2name
199
+ inspect
200
+ to_i
201
+ to_int
202
+ to_s
203
+ to_sym
204
+ ].freeze
205
+
206
+ TRUECLASS_METHODS = %w[
207
+ &
208
+ to_s
209
+ ^
210
+ |
211
+ ].freeze
212
+
213
+ FALSECLASS_METHODS = %w[
214
+ &
215
+ to_s
216
+ ^
217
+ |
218
+ ].freeze
219
+
220
+ ENUMERABLE_METHODS = %w[
221
+ all?
222
+ any?
223
+ collect
224
+ detect
225
+ each_with_index
226
+ entries
227
+ find
228
+ find_all
229
+ grep
230
+ include?
231
+ inject
232
+ map
233
+ max
234
+ member?
235
+ min
236
+ partition
237
+ reject
238
+ select
239
+ sort
240
+ sort_by
241
+ to_a
242
+ zip
243
+ ].freeze
244
+
245
+ STRING_METHODS = %w[
246
+ %
247
+ *
248
+ +
249
+ <<
250
+ <=>
251
+ ==
252
+ =~
253
+ capitalize
254
+ capitalize!
255
+ casecmp
256
+ center
257
+ chomp
258
+ chomp!
259
+ chop
260
+ chop!
261
+ concat
262
+ count
263
+ crypt
264
+ delete
265
+ delete!
266
+ downcase
267
+ downcase!
268
+ dump
269
+ each
270
+ each_byte
271
+ each_line
272
+ empty?
273
+ eql?
274
+ gsub
275
+ gsub!
276
+ hash
277
+ hex
278
+ include?
279
+ index
280
+ initialize
281
+ initialize_copy
282
+ insert
283
+ inspect
284
+ intern
285
+ length
286
+ ljust
287
+ lstrip
288
+ lstrip!
289
+ match
290
+ next
291
+ next!
292
+ oct
293
+ replace
294
+ reverse
295
+ reverse!
296
+ rindex
297
+ rjust
298
+ rstrip
299
+ rstrip!
300
+ scan
301
+ size
302
+ slice
303
+ slice!
304
+ split
305
+ squeeze
306
+ squeeze!
307
+ strip
308
+ strip!
309
+ start_with?
310
+ sub
311
+ sub!
312
+ succ
313
+ succ!
314
+ sum
315
+ swapcase
316
+ swapcase!
317
+ to_f
318
+ to_i
319
+ to_s
320
+ to_str
321
+ to_sym
322
+ tr
323
+ tr!
324
+ tr_s
325
+ tr_s!
326
+ upcase
327
+ upcase!
328
+ upto
329
+ []
330
+ []=
331
+ ].freeze
332
+ end
333
+ end
@@ -0,0 +1,226 @@
1
+ require 'rspec'
2
+ require 'sandbox'
3
+
4
+ describe Sandbox do
5
+ after(:each) do
6
+ Object.class_eval { remove_const(:Foo) } if defined?(Foo)
7
+ end
8
+
9
+ describe ".new" do
10
+ subject { Sandbox.new }
11
+
12
+ it { should_not be_nil }
13
+ it { should be_an_instance_of(Sandbox::Full) }
14
+ end
15
+
16
+ describe ".safe" do
17
+ subject { Sandbox.safe }
18
+
19
+ it { should be_an_instance_of(Sandbox::Safe) }
20
+
21
+ it 'should not lock down until calling activate!' do
22
+ subject.eval('`echo hello`').should == "hello\n"
23
+
24
+ subject.activate!
25
+
26
+ expect {
27
+ subject.eval('`echo hello`')
28
+ }.to raise_error(Sandbox::SandboxException)
29
+ end
30
+
31
+ it "should activate FakeFS inside the sandbox (and not allow it to be deactivated)" do
32
+ subject.eval('File').should == ::File
33
+
34
+ subject.activate!
35
+
36
+ foo = File.join(File.dirname(__FILE__), 'support', 'foo.txt')
37
+
38
+ expect {
39
+ subject.eval(%{File.read('#{foo}')})
40
+ }.to raise_error(Sandbox::SandboxException, /Errno::ENOENT: No such file or directory/)
41
+
42
+ subject.eval('File').should == FakeFS::File
43
+ subject.eval('Dir').should == FakeFS::Dir
44
+ subject.eval('FileUtils').should == FakeFS::FileUtils
45
+ subject.eval('FileTest').should == FakeFS::FileTest
46
+
47
+ subject.eval(%{FakeFS.deactivate!})
48
+
49
+ expect {
50
+ subject.eval(%{File.read('#{foo}')})
51
+ }.to raise_error(Sandbox::SandboxException, /Errno::ENOENT: No such file or directory/)
52
+
53
+ subject.eval(%{File.open('/bar.txt', 'w') {|file| file << "bar" }})
54
+
55
+ expect {
56
+ subject.eval(%{FileUtils.cp('/bar.txt', '/baz.txt')})
57
+ }.to_not raise_error(Sandbox::SandboxException, /NoMethodError/)
58
+ end
59
+
60
+ it "sandboxes global variables" do
61
+ subject.eval('$0').should == '(sandbox)'
62
+ /(.)(.)(.)/.match('abc')
63
+ subject.eval('$TEST = "TEST"; $TEST').should == 'TEST'
64
+ subject.eval('/(.)(.)(.)/.match("def"); $2').should == 'e'
65
+ $2.should == 'b'
66
+ subject.eval('$TEST').should == 'TEST'
67
+ subject.eval('$2').should == 'e'
68
+ /(.)(.)(.)/.match('ghi')
69
+ end
70
+ end
71
+
72
+ describe ".current" do
73
+ it "should not return a current sandbox outside a sandbox" do
74
+ Sandbox.current.should be_nil
75
+ end
76
+
77
+ it "should return the current sandbox inside a sandbox" do
78
+ pending do
79
+ sandbox = Sandbox.new
80
+ sandbox.ref(Sandbox)
81
+ sandbox.eval('Sandbox.current').should == sandbox
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "#eval" do
87
+ subject { Sandbox.new }
88
+
89
+ it "should allow a range of common operations" do
90
+ operations = <<-OPS
91
+ 1 + 1
92
+ 'foo'.chomp
93
+ 'foo'
94
+ OPS
95
+ subject.eval(operations).should == 'foo'
96
+ end
97
+
98
+ it "should persist state between evaluations" do
99
+ subject.eval('o = Object.new')
100
+ subject.eval('o').should_not be_nil
101
+ end
102
+
103
+ it "should be able to define a new class in the sandbox" do
104
+ result = subject.eval('Foo = Struct.new(:foo); struct = Foo.new("baz"); struct.foo')
105
+ result.should == 'baz'
106
+ end
107
+
108
+ it "should be able to use a class across invocations" do
109
+ # Return nil, because the environment doesn't know "Foo"
110
+ subject.eval('Foo = Struct.new(:foo); nil')
111
+ subject.eval('struct = Foo.new("baz"); nil')
112
+ subject.eval('struct.foo').should == 'baz'
113
+ end
114
+
115
+ describe "communication between sandbox and environment" do
116
+ it "should be possible to pass data from the box to the environment" do
117
+ Foo = Struct.new(:foo)
118
+ subject.ref(Foo)
119
+ struct = subject.eval('struct = Foo.new')
120
+ subject.eval('struct.foo = "baz"')
121
+ struct.foo.should == 'baz'
122
+ end
123
+
124
+ it "should be possible to pass data from the environment to the box" do
125
+ Foo = Struct.new(:foo)
126
+ subject.ref(Foo)
127
+ struct = subject.eval('struct = Foo.new')
128
+ struct.foo = 'baz'
129
+ subject.eval('struct.foo').should == 'baz'
130
+ end
131
+
132
+ it "should be able to pass large object data from the box to the environment" do
133
+ expect {
134
+ subject.eval %{
135
+ (0..1000).to_a.inject({}) {|h,i| h[i] = "HELLO WORLD"; h }
136
+ }
137
+ }.to_not raise_error(Sandbox::SandboxException)
138
+
139
+ expect {
140
+ subject.eval %{'RUBY'*100}
141
+ }.to_not raise_error(Sandbox::SandboxException)
142
+ end
143
+ end
144
+ end
145
+
146
+ describe "#import" do
147
+ subject { Sandbox.new }
148
+
149
+ it "should be able to call a referenced namespaced module method" do
150
+ Foo = Class.new
151
+ Foo::Bar = Module.new do
152
+ def baz
153
+ 'baz'
154
+ end
155
+ module_function :baz
156
+ end
157
+
158
+ subject.import(Foo::Bar)
159
+ subject.eval('Foo::Bar.baz').should == 'baz'
160
+ end
161
+
162
+ it "should be able to include a module from the environment" do
163
+ Foo = Module.new do
164
+ def baz
165
+ 'baz'
166
+ end
167
+ end
168
+
169
+ subject.import(Foo)
170
+ subject.eval("class Bar; include Foo; end; nil")
171
+ subject.eval('Bar.new.baz').should == 'baz'
172
+ end
173
+
174
+ it "should be able to copy instance methods from a module that uses module_function" do
175
+ Foo = Module.new do
176
+ def baz; 'baz'; end
177
+
178
+ module_function :baz
179
+ end
180
+
181
+ subject.import Foo
182
+ subject.eval('Foo.baz').should == 'baz'
183
+ end
184
+ end
185
+
186
+ describe "#ref" do
187
+ subject { Sandbox.new }
188
+
189
+ it "should be possible to reference a class defined outside the box" do
190
+ Foo = Class.new
191
+ subject.ref(Foo)
192
+ subject.eval('Foo.new').should be_an_instance_of(Foo)
193
+ end
194
+
195
+ it "should be possible to change the class after the ref" do
196
+ Foo = Class.new
197
+ subject.ref(Foo)
198
+ def Foo.foo; 'baz'; end
199
+ subject.eval('Foo.foo').should == 'baz'
200
+ end
201
+
202
+ it "should be possible to dynamically add a class method after the ref" do
203
+ Foo = Class.new
204
+ subject.ref(Foo)
205
+ Foo.class_eval('def Foo.foo; "baz"; end')
206
+ subject.eval('Foo.foo').should == 'baz'
207
+ end
208
+
209
+ it "should be possible to dynamically add a class method after the ref" do
210
+ Foo = Class.new
211
+ subject.ref(Foo)
212
+ Foo.instance_eval('def Foo.foo; "baz"; end')
213
+ subject.eval('Foo.foo').should == 'baz'
214
+ end
215
+
216
+ it "should be possible to call a method on the class that receives a block" do
217
+ Foo = Class.new do
218
+ def self.bar
219
+ yield
220
+ end
221
+ end
222
+ subject.ref(Foo)
223
+ subject.eval(%{Foo.bar { "baz" }}).should == 'baz'
224
+ end
225
+ end
226
+ end
@@ -0,0 +1 @@
1
+ foo
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jruby_sandbox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: java
7
+ authors:
8
+ - Dray Lacy
9
+ - Eric Allam
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2011-09-15 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: fakefs
17
+ requirement: &70097860842780 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *70097860842780
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: &70097860842160 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *70097860842160
37
+ - !ruby/object:Gem::Dependency
38
+ name: rake-compiler
39
+ requirement: &70097860841480 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *70097860841480
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec
50
+ requirement: &70097860840900 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *70097860840900
59
+ - !ruby/object:Gem::Dependency
60
+ name: yard
61
+ requirement: &70097860840300 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *70097860840300
70
+ description: A version of _why's Freaky Freaky Sandbox for JRuby.
71
+ email:
72
+ - dray@envylabs.com
73
+ - eric@envylabs.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - .gitignore
79
+ - .rvmrc
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - README.md
83
+ - Rakefile
84
+ - ext/java/sandbox/BoxedClass.java
85
+ - ext/java/sandbox/SandboxFull.java
86
+ - ext/java/sandbox/SandboxModule.java
87
+ - ext/java/sandbox/SandboxProfile.java
88
+ - ext/java/sandbox/SandboxService.java
89
+ - jruby_sandbox.gemspec
90
+ - lib/sandbox.rb
91
+ - lib/sandbox/prelude.rb
92
+ - lib/sandbox/version.rb
93
+ - spec/sandbox_spec.rb
94
+ - spec/support/foo.txt
95
+ - lib/sandbox/sandbox.jar
96
+ homepage: http://github.com/omghax/jruby_sandbox
97
+ licenses: []
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubyforge_project: jruby_sandbox
116
+ rubygems_version: 1.8.10
117
+ signing_key:
118
+ specification_version: 3
119
+ summary: Sandbox support for JRuby
120
+ test_files:
121
+ - spec/sandbox_spec.rb
122
+ - spec/support/foo.txt