Skip to content

Commit

Permalink
Merge pull request #42 from escitalopram/github-sandboxed-invocations
Browse files Browse the repository at this point in the history
Allow sandboxed Invocable::invoke*
  • Loading branch information
mxro authored Feb 21, 2018
2 parents 653f859 + c269dc4 commit 369bd11
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 49 deletions.
9 changes: 8 additions & 1 deletion src/main/java/delight/nashornsandbox/NashornSandbox.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.concurrent.ExecutorService;

import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptException;

Expand Down Expand Up @@ -245,5 +246,11 @@ public interface NashornSandbox {
* @return
*/
Bindings createBindings();


/**
* Returns an {@link Invocable} instance, so that method invocations are also sandboxed.
* @return
*/
Invocable getSandboxedInvocable();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package delight.nashornsandbox.internal;

import static delight.nashornsandbox.internal.NashornSandboxImpl.LOG;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

public class EvaluateOperation implements ScriptEngineOperation {

private final String js;
private final ScriptContext scriptContext;

public String getJs() {
return js;
}

public ScriptContext getScriptContext() {
return scriptContext;
}

public EvaluateOperation(String js, ScriptContext scriptContext) {
this.js = js;
this.scriptContext = scriptContext;
}

@Override
public Object executeScriptEngineOperation(ScriptEngine scriptEngine) throws ScriptException {
if (LOG.isDebugEnabled()) {
LOG.debug("--- Running JS ---");
LOG.debug(js);
LOG.debug("--- JS END ---");
}

if (scriptContext != null) {
return scriptEngine.eval(js, scriptContext);
} else {
return scriptEngine.eval(js);
}
}

}
27 changes: 27 additions & 0 deletions src/main/java/delight/nashornsandbox/internal/InvokeOperation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package delight.nashornsandbox.internal;

import javax.script.Invocable;
import javax.script.ScriptEngine;

public class InvokeOperation implements ScriptEngineOperation {

private final Object thisObj;
private final String name;
private final Object[] args;

public InvokeOperation(Object thisObj, String name, Object[] args) {
this.thisObj = thisObj;
this.name = name;
this.args = args;
}

@Override
public Object executeScriptEngineOperation(ScriptEngine scriptEngine) throws Exception {
if (thisObj == null) {
return ((Invocable)scriptEngine).invokeFunction(name, args);
} else {
return ((Invocable)scriptEngine).invokeMethod(thisObj, name, args);
}
}

}
34 changes: 5 additions & 29 deletions src/main/java/delight/nashornsandbox/internal/JsEvaluator.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package delight.nashornsandbox.internal;

import static delight.nashornsandbox.internal.NashornSandboxImpl.LOG;

import java.util.concurrent.ExecutorService;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;

/**
Expand All @@ -19,16 +16,16 @@
*/
class JsEvaluator implements Runnable {
private final ThreadMonitor threadMonitor;
private String js;
private final ScriptEngine scriptEngine;

private Object result = null;
private Exception exception = null;
private ScriptContext scriptContext = null;
JsEvaluator(final ScriptEngine scriptEngine, final long maxCPUTime, final long maxMemory) {
private final ScriptEngineOperation operation;

JsEvaluator(final ScriptEngine scriptEngine, final long maxCPUTime, final long maxMemory, ScriptEngineOperation operation) {
this.scriptEngine = scriptEngine;
this.threadMonitor = new ThreadMonitor(maxCPUTime, maxMemory);
this.operation = operation;
}

boolean isScriptKilled() {
Expand All @@ -54,17 +51,7 @@ void runMonitor() {
public void run() {
try {
threadMonitor.setThreadToMonitor(Thread.currentThread());
if (LOG.isDebugEnabled()) {
LOG.debug("--- Running JS ---");
LOG.debug(js);
LOG.debug("--- JS END ---");
}

if (scriptContext != null) {
result = scriptEngine.eval(js, scriptContext);
} else {
result = scriptEngine.eval(js);
}
result = operation.executeScriptEngineOperation(scriptEngine);
}
catch (final RuntimeException e) {
// InterruptedException means script was successfully interrupted,
Expand All @@ -81,11 +68,6 @@ public void run() {
threadMonitor.stopMonitor();
}
}

/**Set JavaScrip text to be evaluated. */
void setJs(final String js) {
this.js = js;
}

Exception getException() {
return exception;
Expand All @@ -94,10 +76,4 @@ Exception getException() {
Object getResult() {
return result;
}

/** Set ScriptContext to set set different scopes to evaluate */
void setScriptContext(ScriptContext scriptContext) {
this.scriptContext = scriptContext;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.concurrent.ExecutorService;

import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
Expand Down Expand Up @@ -66,6 +67,8 @@ public class NashornSandboxImpl implements NashornSandbox {

protected boolean engineAsserted;

protected Invocable lazyInvocable;

/** The size of the LRU cache of prepared statemensts. */
protected int maxPreparedStatements;

Expand Down Expand Up @@ -116,30 +119,23 @@ public Object eval(final String js) throws ScriptCPUAbuseException, ScriptExcept
@Override
public Object eval(final String js, final ScriptContext scriptContext)
throws ScriptCPUAbuseException, ScriptException {
final JsSanitizer sanitizer = getSanitizer();
final String securedJs = sanitizer.secureJs(js);
EvaluateOperation op = new EvaluateOperation(securedJs, scriptContext);
return executeSandboxedOperation(op);
}

private Object executeSandboxedOperation(ScriptEngineOperation op) throws ScriptCPUAbuseException, ScriptException {
if (!engineAsserted) {
engineAsserted = true;
assertScriptEngine();
}
try {
if (maxCPUTime == 0 && maxMemory == 0) {
if (LOG.isDebugEnabled()) {
LOG.debug("--- Running JS ---");
LOG.debug(js);
LOG.debug("--- JS END ---");
}

if (scriptContext != null) {
return scriptEngine.eval(js, scriptContext);
}

return this.scriptEngine.eval(js);
return op.executeScriptEngineOperation(scriptEngine);
}
checkExecutorPresence();
final JsSanitizer sanitizer = getSanitizer();
final String securedJs = sanitizer.secureJs(js);
final JsEvaluator evaluator = getEvaluator();
evaluator.setJs(securedJs);
evaluator.setScriptContext(scriptContext);
final JsEvaluator evaluator = getEvaluator(op);
executor.execute(evaluator);
evaluator.runMonitor();
if (evaluator.isCPULimitExceeded()) {
Expand All @@ -162,8 +158,8 @@ public Object eval(final String js, final ScriptContext scriptContext)
}
}

private JsEvaluator getEvaluator() {
return new JsEvaluator(scriptEngine, maxCPUTime, maxMemory);
private JsEvaluator getEvaluator(ScriptEngineOperation op) {
return new JsEvaluator(scriptEngine, maxCPUTime, maxMemory, op);
}

private void checkExecutorPresence() {
Expand Down Expand Up @@ -299,4 +295,57 @@ public Bindings createBindings() {
return scriptEngine.createBindings();
}

@Override
public Invocable getSandboxedInvocable() {
if (maxMemory == 0 && maxCPUTime == 0) {
return (Invocable)scriptEngine;
}
return getLazySandboxedInvocable();
}

private Invocable getLazySandboxedInvocable() {
if (lazyInvocable == null) {
Invocable sandboxInvocable = new Invocable() {

@Override
public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException {
InvokeOperation op = new InvokeOperation(thiz, name, args);
try {
return executeSandboxedOperation(op);
} catch (ScriptException e) {
throw e;
} catch (Exception e) {
throw new ScriptException(e);
}
}

@Override
public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
InvokeOperation op = new InvokeOperation(null, name, args);
try {
return executeSandboxedOperation(op);
} catch (ScriptException e) {
throw e;
} catch (Exception e) {
throw new ScriptException(e);
}
}

@Override
public <T> T getInterface(Object thiz, Class<T> clasz) {
// TODO add proxy wrapper for proper sandboxing
throw new IllegalStateException("Not yet implemented");
}

@Override
public <T> T getInterface(Class<T> clasz) {
// TODO add proxy wrapper for proper sandboxing
throw new IllegalStateException("Not yet implemented");
}
};
lazyInvocable = sandboxInvocable;
}
return lazyInvocable;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package delight.nashornsandbox.internal;

import javax.script.ScriptEngine;
import javax.script.ScriptException;

public interface ScriptEngineOperation {

Object executeScriptEngineOperation(ScriptEngine scriptEngine) throws ScriptException, Exception;

}
34 changes: 34 additions & 0 deletions src/test/java/delight/nashornsandbox/TestInvocable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package delight.nashornsandbox;

import static org.junit.Assert.assertEquals;

import javax.script.Invocable;
import javax.script.ScriptException;

import org.junit.Test;

import delight.nashornsandbox.exceptions.ScriptCPUAbuseException;

public class TestInvocable {

@Test
public void testInvokeFunction() throws ScriptCPUAbuseException, ScriptException, NoSuchMethodException {
final NashornSandbox sandbox = NashornSandboxes.create();
final String script = "function x(){return 1;}\n";
sandbox.eval(script);
Invocable invocable = sandbox.getSandboxedInvocable();
assertEquals(1, invocable.invokeFunction("x"));
}

@Test
public void testInvokeMethod() throws ScriptCPUAbuseException, ScriptException, NoSuchMethodException {
final NashornSandbox sandbox = NashornSandboxes.create();
final String script = "var obj = {n: 1, x:function(arg){return this.n + arg;}};";
sandbox.eval(script);
Object thisobj = sandbox.get("obj");
Invocable invocable = sandbox.getSandboxedInvocable();

assertEquals(3.0, invocable.invokeMethod(thisobj, "x", 2));
}

}
26 changes: 25 additions & 1 deletion src/test/java/delight/nashornsandbox/TestLimitCPU.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package delight.nashornsandbox;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.util.concurrent.Executors;

import javax.script.Invocable;
import javax.script.ScriptException;

import org.junit.Test;
Expand Down Expand Up @@ -197,5 +199,27 @@ public void testIsMatchCpuAbuseDirect() {
}
}


@Test
public void testCpuLmitInInvocable() throws ScriptCPUAbuseException, ScriptException, NoSuchMethodException {
final NashornSandbox sandbox = NashornSandboxes.create();
sandbox.setMaxCPUTime(50);
sandbox.setExecutor(Executors.newSingleThreadExecutor());
try {
final String badScript = "function x(){while (true){};}\n";
try {
sandbox.eval(badScript);
} catch (ScriptCPUAbuseException e) {
fail("we want to test invokeFunction(), but we failed too early");
}
Invocable invocable = sandbox.getSandboxedInvocable();
try {
invocable.invokeFunction("x");
fail("expected an exception for the infinite loop");
} catch (ScriptException e) {
assertEquals(ScriptCPUAbuseException.class, e.getCause().getClass());
}
} finally {
sandbox.getExecutor().shutdown();
}
}
}

0 comments on commit 369bd11

Please sign in to comment.