Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AccessController not taking a class' ProtectionDomain into account

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 the doPrivileged(...) block it returns silently
  • When I call System.getSecurityManager().checkPermission(...), which forwards the request to the AccessController, then the AccessController throws an exception.
  • The ProtectionDomain seems to get lost when calling AccessController through the SecurityManager?
  • Restricted file actions (like creating a java.io.FileReader), directly call the SecurityManager rather than the AccessController. How do I get the AccessController, when called through the SecurityManager, to respect the ProtectionDomain of the class that invoked the doRestricted(...)-block?
  • Could it be that the SecurityManager itself doesn't have the required permissions? Thereby, by being sandwiched into the call-stack between the privileged code, and the AccessController generates 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.

like image 969
Sebastian Probst Eide Avatar asked Dec 11 '12 11:12

Sebastian Probst Eide


2 Answers

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:

+-----------+-------------------+-----------------------+
| Callstack | Class permissions | Permissions in effect |
+-----------+-------------------+-----------------------+
| Some      | {Read,Write}      | {Read}                |
| Other     | {Read}            | {Read}                |
+-----------+-------------------+-----------------------+

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 query at the top, in effect has no permissions.

+-----------+-------------------+-----------------------+
| Callstack | Class permissions | Permissions in effect |
+-----------+-------------------+-----------------------+
| Query     | {Read}            | {}                    |
| Other     | {}                | {}                    |
+-----------+-------------------+-----------------------+

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:

+-----------+-------------------+-----------------------+
| Callstack | Class permissions | Permissions in effect |
+-----------+-------------------+-----------------------+
| Query     | {Read}            | {Read}                |
| Other     | {}                | {}                    |
+-----------+-------------------+-----------------------+

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:

+-----------------+-------------------+-----------------------+
|    Callstack    | Class permissions | Permissions in effect |
+-----------------+-------------------+-----------------------+
| SecurityManager | {}                | {}                    |
| Query           | {Read}            | {Read}                |
| Other           | {}                | {}                    |
+-----------------+-------------------+-----------------------+

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:

+-----------------+---------------------+-----------------------+
|    Callstack    |  Class permissions  | Permissions in effect |
+-----------------+---------------------+-----------------------+
| SecurityManager | {Read,Write,Delete} | {Read}                |
| Query           | {Read}              | {Read}                |
| Other           | {}                  | {}                    |
+-----------------+---------------------+-----------------------+

Phew! That was quite a mouthful! Hope this was useful to someone out there :)

like image 149
Sebastian Probst Eide Avatar answered Oct 20 '22 15:10

Sebastian Probst Eide


I think the issue here is with how you provide ProtectionDomain for SecurityManager.

If you want to load classes yourself, and be able to use SM you should extend SecureClassLoader. This class provides method with template:

protected Class defineClass(String name, byte[] b, int off, int len, CodeSource cs) 

In which you should provide a CodeSource for your class. And then the other method:

   protected  PermissionCollection getPermissions(CodeSource codesource) 

Which will return PermissionCollection for classes from given CodeSource. That's the way you should implement dynamic permissions for dynamicly loaded classes.

like image 37
damiankolasa Avatar answered Oct 20 '22 15:10

damiankolasa