I want to allow scripts in a sandbox to access system properties but I get AccessControlException
s. 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 ProtectionDomain
s 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?
Have a look in ProtectionDomain.implies()
:
if (!staticPermissions &&
Policy.getPolicyNoCheck().implies(this, permission))
return true;
This means that for dynamic ProtectionDomain
s, 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.