Search code examples
javasecuritysecuritymanageraccesscontrolexception

Why does this seemingly equivalent SecurityManager code cause spurious exceptions?


This question is a follow-up to my previous question about unusual exceptions generated by a custom security manager. At a high level, I am interested in building an application that runs trusted code alongside untrusted code. My initial idea was to build a custom SecurityManager that would disallow most operations from running. This led to unusual behavior where trusted reflective code instantiating untrusted objects failed after 16 invocations.

I have rewritten the code so that instead of using a custom SecurityManager to handle this, I instead create a new protection domain in which the untrusted code runs and then strip permissions from that untrusted code. This new code is shown here:

import java.io.FilePermission;
import java.lang.reflect.*;
import java.security.*;

public class Main {
    /* Track how many instances have been created so that we can see when the exception
     * is thrown.
     */
    private static int numInstances = 0;
    public Main() {
        System.out.println("Number created: " + ++numInstances);
    }

    /* Utility function that returns a Constructor object for main. */
    private static Constructor<Main> getCtor() {
        try {
            return Main.class.getConstructor();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            System.exit(-1);
            return null; // Unreachable, needed to appease compiler.
        }
    }

    /* Utility function that creates an AccessControlContext that only has file
     * read permissions.
     */
    private static AccessControlContext getContext() {
        CodeSource c = new CodeSource(null, (java.security.cert.Certificate[])null);
        Permissions permissions = new Permissions();

        /* Grant specific permission to read files. This is necessary, since otherwise the
         * class loader can't read classes from disk.
         */
        permissions.add(new FilePermission("*", "read"));

        /* Construct an AccessControlContext from these permissions. */
        return new AccessControlContext(new ProtectionDomain[] {new ProtectionDomain(c, permissions)});
    }

    public static void main(String[] args) {
        /* Get a very restrictive AccessControlContext that does not allow for anything to run. */
        AccessControlContext noPermissions = getContext();

        /* Install a standard security manager to enable security. */
        System.setSecurityManager(new SecurityManager());

        /* Sit in an infinite loop using reflection to create Main objects.  This code is
         * run in a context where its only permissions are file reading.
         */
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            @Override
            public Void run() {
                /* Continuously create new Main objects. */
                Constructor<Main> ctor = getCtor();
                try {
                    while (true) {
                        ctor.newInstance();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }
        }, noPermissions);

    }
}

This code now works perfectly fine - it constructs all sorts of Main objects without any trouble whatsoever.

What I'm confused about is the following. In order for the AccessController to have any teeth, we need to have the security manager turned on. I do this by calling

/* Install a standard security manager to enable security. */
System.setSecurityManager(new SecurityManager());

Now, suppose I change this from the default SecurityManager to this custom SecurityManager:

/* Install a standard security manager to enable security. */
System.setSecurityManager(new SecurityManager() {
    @Override
    public void checkPermission(Permission p) {
         /* Log the permission. */
         System.out.println("Checking " + p);
         super.checkPermission(p);
    }
});

This SecurityManager is identical to before, except that it logs what happens when a permission is checked and then forwards the request to the default SecurityManager.

If I make this change and run the program, I now get the same behavior as before:

Checking ("java.io.FilePermission" "/home/keith/Documents/secret-eclipse-workspace/Security Manager Test/bin/Main$2.class" "read")
Checking ("java.io.FilePermission" "/home/keith/Documents/secret-eclipse-workspace/Security Manager Test/bin/Main$2.class" "read")
Checking ("java.io.FilePermission" "/home/keith/Documents/secret-eclipse-workspace/Security Manager Test/bin/Main$2.class" "read")
Number created: 1
Number created: 2
Number created: 3
Number created: 4
Number created: 5
Number created: 6
Number created: 7
Number created: 8
Number created: 9
Number created: 10
Number created: 11
Number created: 12
Number created: 13
Number created: 14
Number created: 15
Checking ("java.lang.RuntimePermission" "createClassLoader")
java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "createClassLoader")
    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:372)
    at java.security.AccessController.checkPermission(AccessController.java:559)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
    at Main$1.checkPermission(Main.java:51)
    at java.lang.SecurityManager.checkCreateClassLoader(SecurityManager.java:611)
    at java.lang.ClassLoader.checkCreateClassLoader(ClassLoader.java:274)
    at java.lang.ClassLoader.<init>(ClassLoader.java:316)
    at sun.reflect.DelegatingClassLoader.<init>(ClassDefiner.java:72)
    at sun.reflect.ClassDefiner$1.run(ClassDefiner.java:60)
    at sun.reflect.ClassDefiner$1.run(ClassDefiner.java:58)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:57)
    at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:399)
    at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:396)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:395)
    at sun.reflect.MethodAccessorGenerator.generateConstructor(MethodAccessorGenerator.java:94)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:48)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at Main$2.run(Main.java:65)
    at Main$2.run(Main.java:1)
    at java.security.AccessController.doPrivileged(Native Method)
    at Main.main(Main.java:58)

Why am I getting different behavior before and after using this custom SecurityManager? I don't understand why the program would produce different results in these cases, since in both cases the default SecurityManager is the one actually making all of the security checks.

Thanks!


Solution

  • The code in your SecurityManager does not appear to be trusted. So when it appear during stack inspection, the security check will fail.

    Why doesn't the code main and run cause the same problem? We can see from the stack trace those stack elements that are involved in the security check.

    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:372)
    at java.security.AccessController.checkPermission(AccessController.java:559)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
    at Main$1.checkPermission(Main.java:51)
    at java.lang.SecurityManager.checkCreateClassLoader(SecurityManager.java:611)
    at java.lang.ClassLoader.checkCreateClassLoader(ClassLoader.java:274)
    at java.lang.ClassLoader.<init>(ClassLoader.java:316)
    at sun.reflect.DelegatingClassLoader.<init>(ClassDefiner.java:72)
    at sun.reflect.ClassDefiner$1.run(ClassDefiner.java:60)
    at sun.reflect.ClassDefiner$1.run(ClassDefiner.java:58)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:57)
    

    The only non-system frame there is Main$1.checkPermission(Main.java:51). Remove it and the problem disappears.