skiwi
skiwi

Reputation: 69379

Why can I break out of a restricted AccessControlContext with AccessController.doPrivileged?

I'm figuring out how to run external code (from a different JAR) without permissions such that it doesn't harm my own system. I have it almost working, however I've come to find a strange situation where a call to System.exit(0) still kills the whole system.

The idea is that different JARs will be loaded in a multi-threaded environment, so a System.exit(0) succeeding is about the worst that can happen, among a lot of other security risks on the server.

I have the following code (in an SSCEE):

public class RestrictAccessControlContext {
    private static final PermissionCollection ALLOWED_PERMISSIONS = new Permissions();
    static {
        //add permissions
    }
    private static final AccessControlContext RESTRICTED_ACCESS_CONTROL_CONTEXT = 
        new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, ALLOWED_PERMISSIONS)});

    private static void executeSandboxed(final Runnable runnable) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<?> future = executorService.submit(() -> {
            AccessController.doPrivileged((PrivilegedAction<Void>)() -> {
                runnable.run();
                return null;
            }, RESTRICTED_ACCESS_CONTROL_CONTEXT);
        });
        try {
            future.get();
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        } catch (ExecutionException ex) {
            throw new RuntimeException(ex.getCause());
        } finally {
            executorService.shutdown();
        }
    }

    public static void main(String[] args) {
        executeSandboxed(() -> {
            AccessController.doPrivileged((PrivilegedAction<Void>)() -> {
                System.exit(0);
                return null;
            });
        });
    }
}

Which you need to run with as VM arguments:

-Djava.security.manager -Djava.security.policy=src/java.policy

Where java.policy is:

grant {
    permission java.security.AllPermission;
};

The idea is that my own code still has full permissions, but that sandboxed code has restricted (in this case: none) permissions.

However in the given example it just exits, please note that the following variants of the main method do work:

public static void main(String[] args) {
    executeSandboxed(() -> System.exit(0));
}

and

public static void main(String[] args) {
    executeSandboxed(() -> new Thread(() -> System.exit(0)).start());
}

What am I doing wrong here, why is it possible to execute an AccessController.doPrivileged call that raises permissions, when they have been restricted before? I expect one of the following to be true:

  1. There would have been a specific permission for calling AccessController.doPrivileged which could be given.
  2. In a AccessController.doPrivileged block the permissions would be the intersection of the "parent" AccessControlContext and the given permission.

Both options would result in the given code correctly throwing a AccessControlException, but it doesn't happen, why is this the case?

Upvotes: 3

Views: 473

Answers (1)

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147154

You don't have any sandboxed code. Your code, which has full permissions, is using them. Code without the relevant permission would not be able to exit. The solution should be to load the untrusted code with an untrusted ProtectionDomain.

Having a permission to raise permission would be pointless. That would be the equivalent of having all permissions.

Internally in OpenJDK there is a variant of doPrivileged that allows intersection of permissions, but that is for lowering permissions when there are multiple sources of distrust.

Upvotes: 1

Related Questions