CONTENTS | PREV | NEXT Java Security Architecture


4 Access Control Mechanisms and Algorithms


4.1 java.security.ProtectionDomain

This class represents a unit of protection within the Java runtime, and is typically associated with a concept of "principal," where a principal is an entity in the computer system to which permissions (and as a result, accountability) are granted.

A domain conceptually encloses a set of classes whose instances are granted the same set of permissions. Currently, a domain is uniquely identified by a CodeSource, which encapsulates two characteristics of the code running inside the domain: the codebase (java.net.URL), and a set of cryptographic keys (of type java.security.PublicKey) that corresponds to the private keys that signed all code in this domain. Thus, classes signed by the same keys and from the same URL are placed in the same domain.

A domain also encompasses the permissions granted to code in the domain, as determined by the security policy currently in effect.

Classes that have the same permissions but are from different code sources belong to different domains.

A class belongs to one and only one ProtectionDomain.

Note that currently in JDK 1.2, protection domains are initially created "on demand" as a result of class loading. The getProtectionDomain() and setProtectionDomain() methods in java.lang.Class can be used to look up or change the protection domain that is associated with a given class. Note that one must have the appropriate permissions (the RuntimePermissions "Class.getProtectionDomain" and "Class.setProtectionDomain", respectively) to successfully invoke these methods. Note also that resetting a protection domain for a class is expected to be a very rare action done, for example, to dynamically modify the permissions of a class by placing it in a different domain with the same CodeSource but different permissions.

Today all code shipped as part of the JDK is considered system code and run inside the unique system domain. Each applet or application runs in its appropriate domain, determined by its code source.

It is possible to ensure that objects in any non-system domain cannot automatically discover objects in another non-system domain. This partition can be achieved by careful class resolution and loading, for example, using different classloaders for different domains. However, SecureClassLoader (or its subclasses) can, at its choice, load classes from different domains, thus allowing these classes to co-exist within the same name space (as partitioned by a Java classloader).


4.2 java.security.AccessController

The AccessController class is used for three purposes, each of which is described in further detail in sections below:

Any code that controls access to system resources should invoke AccessController methods if it wishes to use the specific security model and access control algorithm utilized by these methods. If, on the other hand, the application wishes to defer the security model to that of the SecurityManager installed at runtime, then it should instead invoke corresponding methods in the SecurityManager class.

For example, the typical way to invoke access control has been the following code (taken from an earlier version of JDK):

 ClassLoader loader = this.getClass().getClassLoader();
        if (loader != null) {
                SecurityManager security = System.getSecurityManager();
            if (security != null) {
                security.checkRead("path/file");
            }
        }
Under the new architecture, the check typically should be invoked whether or not there is a classloader associated with a calling class. It could be simply, for example:





FilePermission perm = new FilePermission("path/file", "read");
AccessController.checkPermission(perm);
The AccessController checkPermission() method examines the current execution context and makes the right decision as to whether or not the requested access is allowed. If it is, this check returns quietly. Otherwise, an AccessControlException (a subclass of java.lang.SecurityException) is thrown.

Note that there are (legacy) cases, for example, in some browsers, where whether there is a SecurityManager installed signifies one or the other security state that may result in different actions being taken. For backward compatibility, the checkPermission method on SecurityManager can be used.

SecurityManager security = System.getSecurityManager();
if (security != null) {
    FilePermission perm = new FilePermission("path/file", "read");
    security.checkPermission(perm);
}
We currently do not change this aspect of the SecurityManager usage, but would encourage application developers to use new techniques introduced in this new version of the JDK in their future programming when the built-in access control algorithm is appropriate.

The default behavior of the SecurityManager checkPermission() method is actually to call the AccessController checkPermission() method. A different a SecurityManager implementation may implement its own secutiry management approach, possibly including the addition of further constraints used in determining whether or not an access is permitted.


4.2.1 Algorithm for Checking Permissions

Suppose access control checking occurs in a thread of computation that has a chain of multiple callers (think of this as multiple method calls that cross the protection domain boundaries), as illustrated in the next figure.

When the checkPermission() method of the AccessController is invoked by the most recent caller (e.g., a method in the File class), the basic algorithm for deciding whether to allow or deny the requested access is as follows.

If any caller in the call chain does not have the requested permission, AccessControlException is thrown, unless the following is true -- a caller whose domain is granted the said permission has been marked as "privileged" (see below) and all parties subsequently called by this caller (directly or indirectly) all have the said permission.
There are obviously two implementation strategies:

The benefit is that checking whether a permission is allowed is simplified and can be faster in many cases. The disadvantage is that, because permission checking occurs much less frequently than cross-domain calls, a large percentage of permission updates are likely to be useless effort.

One potential downside of this approach is performance penalty at permission checking time, although this penalty would have been incurred anyway in the "eager evaluation" approach (albeit at earlier times and spread out among each cross-domain call). Our implementation so far has yielded acceptable performance, so we feel that lazy evaluation is the most economical approach overall.

Therefore, the algorithm for checking permissions is currently implemented as "lazy evaluation". Suppose the current thread traversed m callers, in the order of caller 1 to caller 2 to caller m. Then caller m invoked the checkPermission() method. The basic algorithm checkPermission() uses to determine whether access is granted or denied is the following (see subsequent sections for refinements):

 i = m;
 while (i > 0) {
     if (caller i's domain does not have the permission)
         throw AccessControlException
     else if (caller i is marked as privileged) 
         return;
     i = i - 1;
 };

4.2.2 Handling Privileges

A new, static method in the AccessController class allows code in a class instance to inform the AccessController that a body of its code is "privileged" in that it is solely responsible for requesting access to its available resources, no matter what code caused it to do so.

That is, a caller can be marked as being "privileged" when it calls the beginPrivileged() method. When making access control decisions, the checkPermission() method stops checking if it reaches a caller that was marked as "privileged" via a beginPrivileged call without a context argument (see a subsequent section for information about a context argument). If that caller's domain has the specified permission, no further checking is done and checkPermission returns quietly, indicating that the requested access is allowed. If that domain does not have the specified permission, an exception is thrown, as usual.

The normal use of the "privileged" feature is as follows. Note the use of the try/finally block to ensure the privileged section is always exited:

  somemethod() {
       ...normal code here...
       try {
          AccessController.beginPrivileged();
          // privileged code goes here, for example:
          System.loadLibrary("awt");
       } finally {
          AccessController.endPrivileged();
       }
    ...normal code here...
  }
Some important points about being privileged: Firstly, this concept only exists within a single thread. As soon as the privileged code completes, the privilege is guaranteed to be erased or revoked.

Secondly, in this example, the body of code within try-finally is privileged. However, if it calls less trustworthy code that is less privileged, that code will not gain any privileges as a result; a permission is only granted if the privileged code has the permission and so do all the subsequent callers in the call chain up to the checkPermission() call.

Moreover, in the future, we can consider enriching the construct so that a caller can request that the "privileged" status take effect for only some of the permissions granted in its protection domain. This should further reduce the security impact of making a programming mistake. For example, the fictitious code segment below illustrates how to turn on the privilege of only reading everything in the "/tmp" directory.

    Permission perm = new FilePermission("/tmp/*", "read");
    try {
         AccessController.beginPriviled(perm);
         some sensitive code
    } finally {
         AccessController.endPrivileged(perm);
    }
One drawback of the two examples above is that the developer must be careful to use beginPrivileged() and endPrivileged() in pairs, because forgetting to disable a privilege can be very dangerous. To reduce/eliminate the risk, we have put in additional mechanisms to safeguard this primitive. If, for example, a new language construct is introduced to allow "try" to take an object as argument, we can use it to replace a pair of beginPrivileged and endPrivileged calls.


4.3 Inheritence of Access Control Context

When a thread creates a new thread, a new stack is created. If the current security context was not retained when this new thread was created, then when AccessController.checkPermission() was called inside the new thread, a security decision would be made based solely upon the new thread's context, not taking into consideration that of the parent thread.

This clean stack issue would not be a security problem per se, but it would make the writing of secure code (and especially system code) more prone to subtle errors. For example, a non-expert developer might assume (quite reasonably) that a child thread (e.g., one that does not involve untrusted code) inherits the same security context from the parent thread (e.g., one that involves untrusted code). This would cause unintended security holes when accessing controlled resources from inside the new thread (and then passing the resources along to less trusted code), if the parent context was not in fact saved.

Thus, when a new thread is created, we actually ensure (via thread creation and other code) that it automatically inherits the parent thread's security context at the time of creation of the child thread, in such a way that subsequent checkPermission() calls in the child thread will take into consideration the inherited parent context.

In other words, the logical thread context is expanded to include both the parent context (in the form of an AccessControlContext, described in the next section) and the current context, and the algorithm for checking permissions is expanded to the following. (Recall there are m callers up to the call to checkPermission(), and see the next section for information about the AccessControlContext checkPermission() method.)

 i = m;
 while (i > 0) {
     if (caller i's domain does not have the permission)
         throw AccessControlException
     else if (caller i is marked as privileged) 
         return;
     i = i - 1;
 };

 // Next, check the context inherited when
 // the thread was created. Whenever a new thread is created, the
 // AccessControlContext at that time is
 // stored and associated with the new thread, as the "inherited"
 // context.

 inheritedContext.checkPermission(permission);
Note that this inheritance is transitive so that, for example, a grandchild inherits both from the parent and the grandparent. Also note that the inherited context snapshot is taken when the new child is created, and not when the child is first run. There is no public API change for the inheritance feature.


4.4 java.security.AccessControlContext

Recall that the AccessController.checkPermission() method performs security checks within the context of the current execution thread (including the inherited context). A difficulty arises when such a security check can only be done in a different context. That is, sometimes a security check that should be made within a given context will actually need to be done from within a different context. For example, when one thread posts an event to another thread, the second thread serving the requesting event would not have the proper context to complete access control, if the service requests access to controller resources.

To address this issue, we provide the AccessController getContext() method and AccessControlContext class. The getContext method takes a "snapshot" of the current calling context, and places it in an AccessControlContext object, which it returns. A sample call is the following:





AccessControlContext acc = AccessController.getContext();
This context captures relevant information so that an access control decision can be made by checking, from within a different context, against this context information. For example, one thread can post a request event to a second thread, while also supplying this context information. AccessControlContext itself has a checkPermission method that makes access decisions based on the context it encapsulates, rather than that of the current execution thread. Thus, the second thread can perform an appropriate security check if necessary by invoking the following:





acc.checkPermission(permission);
The above method call is equivalent to performing the same security check in the context of the first thread, even though it is done in the second thread.

There are also times where one or more permissions must be checked against an access control context, but it is unclear a priori which permissions are to be checked. In these cases you can use the beginPrivileged method that takes a context:

  somemethod() {
       ...normal code here...
       try {
          AccessController.beginPrivileged(acc);
          // Code goes here. Any permission checks from this
          // point forward require both the current context and
          // the snapshot's context to have the desired permission.
       } finally {
          AccessController.endPrivileged();
       }
    ...normal code here...
Now the complete algorithm utilized by the AccessController checkPermission method can be given. Suppose the current thread traversed m callers, in the order of caller 1 to caller 2 to caller m. Then caller m invoked the checkPermission method. The algorithm checkPermission uses to determine whether access is granted or denied is the following

 i = m;
 while (i > 0) {
     if (caller i's domain does not have the permission)
         throw AccessControlException
     else if (caller i is marked as privileged) {
         if (a context was specified in the call to beginPrivileged) 
             context.checkPermission(permission);
         return;
     }
     i = i - 1;
 };

 // Next, check the context inherited when
 // the thread was created. Whenever a new thread is created, the
 // AccessControlContext at that time is
 // stored and associated with the new thread, as the "inherited"
 // context.

 inheritedContext.checkPermission(permission);


CONTENTS | PREV | NEXT
Copyright © 1997-1998 Sun Microsystems, Inc. All Rights Reserved.