Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow sandboxed Invocable::invoke* #42

Merged
merged 2 commits into from
Feb 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -110,30 +113,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 @@ -156,8 +152,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 @@ -293,4 +289,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();
}
}
}