AccessController not taking a class' ProtectionDomain into account
Asked Answered
C

2

6

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.

Carrie answered 11/12, 2012 at 11:27 Comment(6)
For completeness insert, your policy file entry for this permission. Also try using SecurityManager.checkPermission insead of AccessController.checkPermissionOosphere
@fatfredyy posted my java.policy file above. Very good catch with using SecurityManager.checkPermission instead of AccessController.checkPermission. I changed it, and now my check throws an AccessControlException as well. Seems like the permissions get lost when passing through the SecurityManager? That completely changes my question. I'll update it accordingly. Thanks!Carrie
Thanks @fatfredyy, I have now changed the question based on your comment. As it turns out, the question is now a completely different one.Carrie
From what I see you did not alterd policy file... how do you provide permiisions for your code ? Is your code loaded from ext java lib path or system java lib path ?Oosphere
For all intents and purposes I get the classes passed to me as byte arrays. Then, in my ClassLoader, I call defineClass(name, binary, protection-domain). The protection-domains are created by the class loader as well, and contain exactly the set of permissions I want that particular class to have.Carrie
@fatfredyy I wonder if the issue might actually be that the SecurityManager itself doesn't have permissions, and it therefore eliminates the permissions otherwise granted the "query" through the doPrivileged block?Carrie
C
5

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 :)

Carrie answered 12/12, 2012 at 8:59 Comment(2)
How do you actually "give the SecurityManager a base set of permissions"? Thanks for your postAvoid
Giving the SecurityManager a base set of permissions is unfortunately a bit cumbersome. The way I do this is by creating a custom class loader. The class loader, when defining the class, provides a protection domain with the base permissions I need.Carrie
O
1

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.

Oosphere answered 11/12, 2012 at 18:40 Comment(1)
Thank you for your answer. Unfortunately it does not work. The SecureClassLoader applies permissions it believes to be correct. Unfortunately these are not the same as the permissions required by my system. I need to be able to finely apply exactly the permissions I need for my code to run, and therefore need to use the defineClass method that allows me to set the PermissionDomain. I managed to solve the problem this morning. The issue was that the SecurityManager itself didn't have enough permissions. I'll write it up as a proper answer for future readers. Thanks again for all the help!Carrie

© 2022 - 2024 — McMap. All rights reserved.