Access Control in Java2 (JDK 1.2)

Lecture notes by Borislav Deianov


This lecture discusses the security mechanism in the Java object-oriented programming language and environment. For the discussion, the following Java concepts are necessary.

Java is also: This last point is perhaps the real reason for Java's popularity --- its ability to run Java applications from WWW pages, so called applets.

Example: A Java game applet. A typical Java game applet draws violence on the screen, reads/writes a highscore file on the disk, and sends information, such as shots fired, to a network game server. To assist with depicting graphic violence, the game may use a third-party library for 3D rendering, with everything running on top of a Java runtime system providing access the screen, file system, and network. In this example let's assume that the game comes from www.game.com, the 3D library from the SGI website www.sgi.com, and that the game server is server.game.com.

Three pieces of code are involved: the game, the 3D library, and the Java system. They may interact as follows: a thread in the game makes a call to the 3D library drawBlood(). The thread continues in the 3D library, eventually making a call to drawPixel() in the Java system.
Java Security Why do we need Java security? What security mechanisms and policies do we want? In our example we do not want the game to be able to read /etc/passwd or to communicate with the domain hackers.org. On the other hand we do want it to be able to paint on the screen, read/write the highscore file and communicate with game servers. We need a mechanism to allow/exclude exactly the things we want. Originally, Java contained no such mechanism. Applets were not allowed to write to disk and were only allowed to communicate with the server they were downloaded from. This policy was too restrictive in practice.

There is a new version of Java called Java2 (previously known as JDK 1.2) that is more flexible. The security mechanism in Java2 defines domains (of code), based on origin and signature. As Java has a global namespace, the code for a Java applet may have different origins (e.g., the Java system class files from the local disk, or the game class files from www.game.org), with origins varying in trustworthiness. Signatures are a cryptographic technique that will be discussed later in the course; for now, think of signatures in terms of their real-world analogues, except completely unforgeable.

In Java2, we have a policy file that grants permissions to domains. This policy file should be written by a security knowledgable person, perhaps the system administrator for a corporate Intranet. In our game example the policy file might look like this (using correct Java2 syntax):

    grant CodeBase "http://www.game.com/-" {
       permission java.lang.RuntimePermission("Screen");
       permission java.net.SocketPermission "server.game.com";
       permission java.io.FilePermission "/home/highscore",
"read,write";
    }
    grant CodeBase "http://www.sgi.com/-", SignedBy "SGI" {
       permission java.lang.RuntimePermission("Screen");
       permission java.io.FilePermission "/lib/3d-data/-", "read";
    }
The above policy file grants "Screen" permissions to the game and the 3D library (specifically, to any code fetched using HTTP from www.game.com and www.sgi.com, respectively); the permission to communicate via socket to a game server and the permission to read/write a highscore file to the game; and the permission to read 3D-data from a directory to the 3D library. (A minus at the end of a path denotes "and all information in all subdirectories")

But it is not enough to define a policy file: permissions also need to be checked. This is done by a programming call to AccessController.checkPermission(P), where P is a permission. For example, the drawPixel Java system method might check that its caller has the "Screen" permission by doing: screenChk:   AccessController.checkPermission(new RuntimePermission("Screen")). This changes the picture above as follows:

Executing checkPermission either succeeds or fails. If it succeeds, then control returns back to the invoker; if it fails, then a security exception is raised. Whether checkPermission succeeds depends on all domains in which the invoking thread is executing and what permission each crossed domain has. For example, in the picture above, the thread is executing in three domains: game, 3D library, and the Java system.

The general rule is the all domains crossed by the current thread must have the required permission. In our example, the permissions held by each domain are as follows (the Java system has all permissions by definition):

Therefore the call to drawBlood() from the game will succeed, even if the Java system does screenChk (as it should), since all three domains hold the required "Screen" permission.

This approach to security is called Stack Introspection, and its concrete Java2 implementation could be stated as the following pseudo-code:

    boolean checkPermission(Permission toCheck) {
        Stack domainStack = getDomainStack();  // domain stack for
current thread
        while( ! domainStack.empty() ) {
            Domain here = domainStack.pop();
            if( ! here.implies(toCheck) ) return false;
        }
        return true;
    }
The pseudo-code above shows how Java2 Stack Introspection, when making a security decision, traverses the call stack of a running thread, requiring that all code on the call stack be from domains granted the required permission. How does this mechanism relate to Access Control Matrices from last lecture?

Below we see the policy file above presented as an ACM, with x denoting that a permission is granted. The subjects, for which checkPermission is invoked, are either domains, or, when the execution thread has crossed more than one domain, nested domains. Our example screenChk is invoked for the subject game+3Dlib+system: after the call to drawBlood() a new subject game+3Dlib is created, containing only permissions common to the subjects game and 3Dlib; finally, after the call to drawPixel(), game+3Dlib+system is created, containing only the permissions common to all three subjects, and this is where screenChk is done.

Subject\Object Screen3D data filesHighscore fileSocket to Server
game x   x x
3Dlib x x    
system x x x x
game+3Dlib x      
game+3Dlib+system x      

Note that in Java2 the subject can change when a method is called (perhaps changing back to the same subject if the method called is in the same domain), and that the active subject always has permissions that correspond to the intersection of the permissions of all domains crossed by the running thread.

A question then arises: How can the 3D library access the 3D data files it requires? As described so far, Java2 should not allow the 3D library to access its data files when used by the game, because the game does not have permission to access those files. Granting the game that access does not seem the right solution to this problem, since it would violate the Principle of Least Privilege: the game domain doesn't need access to the files, only the game+3Dlib subject does.

Java2 solves this problem by providing a mechanism for doing Privilege Amplification: increasing the set of permissions held by a subject to the set of permissions granted to the currently executing domain. Below we can see a figure showing how privilege amplification might be used in our example: the game calls a 3D library routine to draw a house on the screen, and before doing so the 3D library loads the 3D data for the house from a file on disk, causing a checkPermission in the Java system that succeeds (!) because the 3D library is calling it from privileged code (shown as a box):

To do privilege amplification in Java2, the code to be run with amplified privileges is put inside a doPrivileged construct, using the following concrete Java2 syntax (which won't be explained in these notes):
    Object house = AccessController.doPrivileged(
        new PrivilegedAction() {
            public Object run() {
                return readObjectFromDataFiles("house");
            }
        }
    );
Implementing privilege amplification requires a change in our permission checking algorithm: now checkPermission only ensures that the right permission is held by all domains crossed by the executing thread, upto and including the first domain running inside a privileged block. The necessary change to the pseudo-code shown before is highlighted below in bold.
    boolean checkPermission(Permission toCheck) {
        Stack domainStack = getDomainStack();
        while( ! domainStack.empty() ) {
            Domain here = domainStack.pop();
            if( ! here.implies(toCheck) ) return false;
            if( here.isPrivileged() ) return true;
        }
        return true;
    }
Actually the pseudo-code above (and the one shown in class) is not quite correct: as more than one thread may be executing in a domain may be at any given time, isPrivileged must be a predicate on call-stack frames, not on domains. The corrected pseudo-code follows:
    boolean checkPermission(Permission toCheck) {
        Stack callStack = getCallStack();
        while( ! callStack.empty() ) {
            StackFrame here = callStack.pop();
            Domain thisDomain = here.getDomain();
            if( ! thisDomain.implies(toCheck) ) return false;
            if( here.isPrivileged() ) return true;
        }
        return true;
    }

The concept of privilege amplification has an important implication: there are now two persons that use the Java2 security mechanisms and must be careful when it comes to security: the policy-specification writer, and the programmer who creates libraries such as 3D library. These two people must even communicate their decisions, because the policy specification writer should know what code is executed inside doPrivileged blocks in libraries.

Another problem with Java2's mechanism is that it is fairly complicated. It is difficult to know exactly what to put in the policy file and exactly where it is necessary/safe to use doPrivileged blocks. Often users are intimidated by this complexity and prefer not to use the provided security mechanisms at all (e.g. by using a very liberal policy file or placing all their code in a big doPrivileged block).