javasecuritymanageraccesscontrolexception

Granting script under a SecurityManager to access system properties


I want to allow scripts in a sandbox to access system properties but I get AccessControlExceptions. Here is the code:

import static org.junit.Assert.*;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.PropertyPermission;
import org.junit.Test;

public class AccessControlTest {

    @Test
    public void testAccessControl() throws Exception {

        PrivilegedAction<String> action = new PrivilegedAction<String>() {

            @Override
            public String run() {
                System.getProperty( "java.version" ); // doesn't work
                return System.getProperty( "foo" ); // doesn't work
            }
        };


        SecurityManager sm = new SecurityManager() {
            @Override
            public void checkPermission( Permission perm ) {

                if( "setSecurityManager".equals( perm.getName() ) ) {
                    return;
                }

                System.out.println( perm );
                super.checkPermission( perm );
            }
        };

        Permissions perms = new Permissions();
        perms.add( new PropertyPermission( "foo", "read" ) );
//        perms.add( new PropertyPermission( "java.version", "read" ) );

        ProtectionDomain domain = new ProtectionDomain( new CodeSource( null, (Certificate[]) null ), perms );
        AccessControlContext context = new AccessControlContext( new ProtectionDomain[] { domain } );

        try {
            System.setSecurityManager( sm );

            Object result = AccessController.doPrivileged( action, context );
            assertEquals( Boolean.TRUE, result );
        } finally {
            System.setSecurityManager( null );
            System.out.println( "setSecurityManager( null )" );
        }


    }

}

When debugging this, I see this output:

...
policy:   granting (java.lang.RuntimePermission stopThread)
policy:   granting (java.net.SocketPermission localhost:1024- listen,resolve)
policy:   granting (java.util.PropertyPermission java.version read)
...
(java.util.PropertyPermission java.version read)
java.lang.Exception: Stack trace
    at java.lang.Thread.dumpStack(Thread.java:1249)
    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:323)
    at java.security.AccessController.checkPermission(AccessController.java:549)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
    at com.avanon.script.AccessControlTest$2.checkPermission(AccessControlTest.java:39)
    at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1285)
    at java.lang.System.getProperty(System.java:650)
...
access: domain 0 ProtectionDomain  (null <no signer certificates>)
 null
 <no principals>
 java.security.Permissions@78ce5b1c (
 (java.util.PropertyPermission foo read)
)


access: domain 1 ProtectionDomain  (file:/.../project/target-eclipse/test-classes/ <no signer certificates>)
 sun.misc.Launcher$AppClassLoader@5d0385c1
 <no principals>
 java.security.Permissions@6ddf073d (
 (java.io.FilePermission /.../project/target-eclipse/test-classes/- read)
 (java.lang.RuntimePermission exitVM)
)


access: access denied (java.util.PropertyPermission java.version read)

(invoke the code with -Djava.security.debug=all to get the same output)

The first block comes from the global java.policy file that comes with the JRE.

Next block is when the code tried to check the access to java.version.

The last block shows that this fails.

This surprised me because access to the propery is allowed in the policy file.

To help, I enabled the commented out line which adds PropertyPermission for java.version. Now the first System.getProperty( "java.version" ) passes.

But the second one still fails:

access: domain that failed ProtectionDomain  (file:/.../project/target-eclipse/test-classes/ <no signer certificates>)
 sun.misc.Launcher$AppClassLoader@5d0385c1
 <no principals>
 java.security.Permissions@6ddf073d (
 (java.io.FilePermission /.../project/target-eclipse/test-classes/- read)
 (java.lang.RuntimePermission exitVM)
)

I'm really stumped by this. From the code, it seems that Java always checks all ProtectionDomains in order (I have four). If any of them doesn't like your access, it will be denied.

But I don't see how the second domain allows access to any properties, so I was expecting either both to fail or both to succeed.

What am I missing?


Solution

  • Have a look in ProtectionDomain.implies():

    if (!staticPermissions && 
        Policy.getPolicyNoCheck().implies(this, permission))
        return true;
    

    This means that for dynamic ProtectionDomains, the current Policy is applied. When a SecurityManager is installed, the default policy is loaded with some grants that give access to the system properties.

    To enable the same for your ProtectionDomain, you need to use a different constructor:

        public ProtectionDomain(CodeSource codesource,
                PermissionCollection permissions,
                ClassLoader classloader,
                Principal[] principals)
    

    This will get you half-way: The access to the system property will now pass.

    But the second access to the property foo will pass for the first protection domain and fail for the others.

    To fix this, you will have to install your own default policy which allows access to both java.version and foo.

    If you do this, then you don't need a custom ProtectionDomain anymore; the policy will be enough.