jruby_sandbox 0.1.0-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/.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