Context
I am writing a Java system where code is executed in very strict sandboxes. A query (consisting of one or more classes) should only be allowed access to exactly one folder (and subfolders and files contained within the folders) for the duration of its execution.
I enforce sandboxing by using a SecurityManager, and a new ClassLoader per query execution. When defining the classes in the ClassLoader using defineClass, I pass along a ProtectionDomain containing the file read permissions that should be granted.
As not all objets on the call stack have the required privileges, the read actions in the query are run within an AccessController.doPrivileged(...)-block.
Problem
- When I call
AccessController.checkPermission(...)directly from within thedoPrivileged(...)block it returns silently - When I call
System.getSecurityManager().checkPermission(...), which forwards the request to theAccessController, then theAccessControllerthrows an exception. - The
ProtectionDomainseems to get lost when callingAccessControllerthrough theSecurityManager? - Restricted file actions (like creating a
java.io.FileReader), directly call theSecurityManagerrather than theAccessController. How do I get theAccessController, when called through theSecurityManager, to respect theProtectionDomainof the class that invoked thedoRestricted(...)-block? - Could it be that the
SecurityManageritself doesn’t have the required permissions? Thereby, by being sandwiched into the call-stack between the privileged code, and theAccessControllergenerates a privilege union of none?
Below follows a sample section:
AccessController.doPrivileged(new PrivilegedAction<QueryResult>() {
public QueryResult run() {
String location = folderName + "/hello";
FilePermission p = new FilePermission(location, "read");
try {
AccessController.checkPermission(p); // Doesn't raise an exception
System.out.println("AccessController says OK");
System.getSecurityManager().checkPermission(p); // Raises AccessControlException
System.out.println("SecurityManager says OK");
} catch (AccessControlException e) {
System.out.println("### Not allowed to read");
}
return null;
}
});
The output generated by the program, including parts of the stack trace (PATH substituting the long pathname used):
AccessController says OK
Asked for permission: ("java.io.FilePermission" "PATH/hello" "read")
java.security.AccessControlException: access denied ("java.io.FilePermission" "PATH/hello" "read")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:366)
at java.security.AccessController.checkPermission(AccessController.java:560)
at com.aircloak.cloak.security.CloakSecurityManager.checkPermission(CloakSecurityManager.java:40)
at com.dummycorp.queries.ValidQuery$1.run(ValidQuery.java:23)
at com.dummycorp.queries.ValidQuery$1.run(ValidQuery.java:1)
at java.security.AccessController.doPrivileged(Native Method)
at com.dummycorp.queries.ValidQuery.run(ValidQuery.java:16)
at com.aircloak.cloak.security.CloakSecurityManagerTest$1.run(CloakSecurityManagerTest.java:75)
at java.lang.Thread.run(Thread.java:722)
The CloakAccessController.checkPermission(...) implementation might also be of interest. It looks like this:
public void checkPermission(Permission perm) {
if (Thread.currentThread().getId() == this.masterThread) {
return;
} else {
System.out.println("Asked for permission: "+perm.toString());
}
AccessController.checkPermission(perm);
}
What it does it mainly bypassing the security restrictions for the thread that created it.
The contents of my java.policy file are those of a standard MacOSX system. I believe that it doesn’t contain any non-standard changes.
I feel a bit awkward answering my own question, but I figured out the right solution, and think it only right to add it here, so it is documented for the future in case someone stumbles over this question.
TL;DR:
My custom SecurityManager did not have the right permissions. Since it was on the callstack between the class invoking the
doPrivileged(...)-block, and the AccessController, the intersection of the privileges was no privileges at all.Long version
The Java security model works as follows. When the AccessController verifies if a class is allowed to invoke a method or not, it looks at the permissions from the top of the callstack towards the bottom. If each entry in the callstack has the permission, then the action is allowed.
Here is an arbitrary example where everything works out fine:
Now, in the case of my question, the lower layers in the callstack have no permissions at all.
Hence we end up with a picture like this, where the
queryat the top, in effect has no permissions.You get around this problem by using a
doPrivileged(...)-block. This allows the permission search through the callstack to end at the entry invoking the privileged action:This is why everything worked fine when I called the
AccessController.checkPermission(...)from within the query. It did have the correct permissions after all. (Un)fortunately the java API’s (for backwards compatibility), always call the SecurityManager. In my case the SecurityManager had no privileges at all. Since it, in effect, was on the callstack between the query making the privileged call, and the AccessController, the net resulting permissions were none:Solution
The solution was to give the SecurityManager a base set of permissions. As a result, the permissions granted to the Query were indeed the ones needed:
Phew! That was quite a mouthful! Hope this was useful to someone out there 🙂