Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should one use a custom SecurityManager to sandbox javax.script.ScriptEngine?

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:

  • Sandboxing JSR-223
  • Sandboxing with javax.script

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?

like image 698
Geo Avatar asked Nov 14 '13 09:11

Geo


1 Answers

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.

like image 136
Tom Hawtin - tackline Avatar answered Oct 11 '22 16:10

Tom Hawtin - tackline