A big issue with the javax.script package of JSR-223 is the lack of any obvious way to sandbox the script that gets run. So the obvious question is: How do you sandbox JSR-223 scripts? That has been asked and there have even been a few attempts at answers.
Here are two interesting questions that ask about this problem but unfortunately miss the point:
The point is that this isn't simply a matter of setting up the right security policy or using the right ClassLoader because the code that you are trying to secure isn't Java code and it has no class. You can try to secure the ScriptEngine by using a ClassLoader to give it a special ProtectionDomain, but that only works as long as the system ClassLoader can't find the ScriptEngine to defeat your efforts by loading the class with the wrong ProtectionDomain, which is bound to happen with any ScriptEngine that ever gets helpfully included as part of the JRE.
Here's another resource that looks good but misses the point: Simple JVM sandboxing. It tells the unwary that they can sandbox their scripts by using doPrivileged
in a custom AccessControlContext that contains a ProtectionDomain with very few Permissions, but of course doing that is meaningless because doPrivileged
is only useful for gaining Permissions, and not useful for denying Permissions. If the untrusted code is already in a sandbox ProtectionDomain then the doPrivileged
trick won't do anything at all, and if the untrusted code is in a non-sandbox ProtectionDomain then it can just call doPrivileged
and completely bypass the sandboxing attempt.
The real question is how does one work around these problems? Assuming we intend to use ProtectionDomains, it seems that the only option is to give ScriptEngineManager a custom ClassLoader that deliberately shadows some classes from the system ClassLoader. In that case, how do we decide which classes to put into the sandbox and which to get from the system ClassLoader? There doesn't seem to be any reliable way of knowing which classes might be responsible for giving the script the means to break out of the sandbox, especially for ScriptEngines that don't exist yet.
The only alternative that I can think of is what I really want to ask about. Would it be a better solution to simply ignore ProtectionDomains and implement a custom SecurityManager that has a sandbox mode for evaluating scripts? For example:
public final class SandboxMan extends SecurityManager {
private int sandboxDepth = 0;
@Override public void checkPermission(Permission permission) {
if(sandboxDepth > 0) throw new SecurityException("Sandboxed: " + permission);
else super.checkPermission(permission);
}
@Override public void checkPermission(Permission permission, Object context) {
if(sandboxDepth > 0) throw new SecurityException("Sandboxed: " + permission);
else super.checkPermission(permission, context);
}
public Object eval(ScriptEngine engine, String script) throws ScriptException {
if(sandboxDepth == Integer.MAX_VALUE) throw new SecurityException("Sandbox depth");
sandboxDepth++;
try {
return engine.eval(script);
} finally { sandboxDepth--; }
}
}
That looks tricky and dangerous. It's dangerous to try to be tricky when security is involved, but could this really be the best solution given the situation?
The two-argument doPrivileged
can be used for reducing privileges. Supply the method with an acc with just the permissions you want to give.
If you start off the script engine with a reduced set of permissions, permission checks will see the reduced set. The likes of AccessController.doPrivileged
and Method.invoke
are taken care of. This, of course, requires that the the particular script engine implementation is implemented correctly. Bugs in the JDK implementation can be reported in the usual way.
You are still left with running untrusted code. Even as bytecode, that can be tricky to guard against in Java.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With