The Artima Developer Community
Sponsored Link

Chapter 3 of Inside the Java Virtual Machine
Security
by Bill Venners

<<  Page 15 of 17  >>

Advertisement

The doPrivileged() Method

The basic algorithm illustrated so far in this chapter, in which the AccessController inspects the stack from top to bottom, stubbornly requiring that every frame have permission to perform an action, prevents less trusted code from hiding behind more trusted code. Because the AccessController looks all the way down the call stack, it will eventually find any method that isn't trusted to perform the requested action. For example, even though the untrusted Stranger object of Example2b places the trusted code of Friend and TextFileDisplayer between it and the Java API method that attempts to open file answer.txt, the untrusted Stranger code is unable to hide behind that trusted code. As shown in Figure 3-7, although the AccessController must look through eight frames that have permission to read answer.txt before it encounters frame two, it eventually reaches frame two. And once it arrives at frame two, it discovers the doYourThing() method of class Stranger, whose associated protection domain doesn't have permission to read answer.txt. As a result of this discovery, the AccessController throws an AccessControllerException, thereby disallowing the read.

The basic AccessController algorithm prevents any code from performing or causing to be performed any action that the code is not trusted to do. Methods belonging to a less powerful protection domain, therefore, are unable to gain privileges by invoking methods belonging to more powerful protection domains. The basic algorithm also implies that methods belonging to more powerful protection domains must give up privileges when calling methods belonging to less powerful protection domains. Although the basic algorithm provides behavior that is desirable in general, the AccessController's stubborn insistence that all frames on the call stack have permission to perform the requested action can at times be a bit restrictive.

Sometimes code farther up the call stack (closer to the top of the stack) might wish to perform an action that code farther down the call stack may not be allowed to do. For example, imagine that an untrusted applet asks the Java API to render a string of text in bold Helvetica font on its applet panel. To fulfill this request, the Java API may need to open a font file on the local disk to load a bold Helvetica font with which to render the text on behalf of the applet. The class making the explicit request to open the font file, because it belongs to the Java API, likely has permission to open the file. However, the code of the untrusted applet, which is represented by a stack frame farther down the call stack, likely doesn't have permission to open the file. Given just the basic algorithm, the AccessController would prevent the opening of the font file because the code for the untrusted applet, sitting somewhere on the stack, doesn't have permission to open the file.

To enable trusted code to perform actions for which less trusted code farther down the call stack may not have permission to do, the AccessController class offers four overloaded static methods named doPrivileged(). Each of these methods accepts as a parameter an object that implements either the java.security.PrivilegedAction or java.security.PrivilegedExceptionAction interface. Both of these interfaces declare one method named run() that takes no parameters and returns void. The only difference between these two interfaces is that whereas PrivilegedExceptionAction's run() method declares Exception in its throws clause, PrivilegedAction declares no throws clause. To perform an action despite the existence of less trusted code farther down the call stack, you create an object that implements one of the PrivilegedAction interfaces, whose run() method performs the action, and pass that object to doPrivileged().

When you invoke doPrivileged(), as when you invoke any method, a new frame is pushed onto the stack. In the context of a stack inspection by the AccessController, a frame for a doPrivileged() method invocation signals an early termination point for the inspection process. If the protection domain associated with the method that invoked doPrivileged() has permission to perform the requested action, the AccessController returns immediately. It allows the action even if code farther down the stack doesn't have permission to perform the action.

If an untrusted applet asks the Java API to render a test string on its applet panel, therefore, the Java API code can open the local font file by wrapping the file open action in a doPrivileged() call. The AccessController will allow such a request even though the untrusted applet code doesn't have permission to open the file. Because the frame for the untrusted applet code is below the frame for the doPrivileged() invocation by the Java API code, the AccessController won't even consider the permissions of the untrusted applet code.

For an example of a doPrivileged() method invocation, consider again the doYourThing() method of class Friend:

// On CD-ROM in file
// security/ex2/com/artima/security/friend/Friend.java
package com.artima.security.friend;
import com.artima.security.doer.Doer;
import java.security.AccessController;
import java.security.PrivilegedAction;

public class Friend implements Doer {

    private Doer next;
    private boolean direct;

    public Friend(Doer next, boolean direct) {
        this.next = next;
        this.direct = direct;
    }

    public void doYourThing() {

        if (direct) {

            next.doYourThing();
        }
        else {
            AccessController.doPrivileged(
                new PrivilegedAction() {
                    public Object run() {
                        next.doYourThing();
                        return null;
                    }
                }
            );
        }
    }
}

If the direct instance variable is false, Friend's doYourThing() method will simply invoke doYourThing() directly on the next reference. But if direct is true, doYourThing() will wrap the invocation of doYourThing() on the next reference in a doPrivileged() call. To do so, Friend instantiates an anonymous inner class that implements PrivilegedAction whose run() method invokes doYourThing() on next, and passes that object to doPrivileged().

To see Friend's doPrivileged() invocation in action, consider the Example2c application from the security/ex2 directory of the CD-ROM:

// On CD-ROM in file security/ex2/Example2c.java
import com.artima.security.friend.Friend;
import com.artima.security.stranger.Stranger;

// This succeeds because Friend code executes a
// doPrivileged() call. (Passing false as
// the second arg to Friend constructor causes
// it to do a doPrivileged().)

class Example2c {

    public static void main(String[] args) {

        TextFileDisplayer tfd = new TextFileDisplayer("answer.txt");

        Friend friend = new Friend(tfd, false);

        Stranger stranger = new Stranger(friend, true);

        stranger.doYourThing();
    }
}

Only one difference exists between the main() method of the Example2c application and the main() method of the previous example, Example2b. When the Example2b application instantiated the Friend object, it passed true as the second parameter. Example2c passes false. If you look back at the code for Friend (and Stranger) shown earlier in this chapter, you'll see that this parameter is used to decide whether to invoke doYourThing() directly on the Doer passed as the first parameter to the constructor. Because Example3c passes false, the Friend class will not invoke doYourThing() directly, but will invoke it indirectly via an AccessController.doPrivileged() invocation.

When the Example2c program invokes doYourThing() on the Stranger object referenced from the stranger variable, the Stranger object invokes doYourThing() on the Friend object, which (because direct is false) invokes doPrivileged(), passing in the anonymous inner class instance that implements PrivilegedAction. The doPrivileged() method invokes run() on the passed PrivilegedAction object, which invokes doYourThing() on the TextFileDisplayer object.

As in the previous example, TextFileDisplayer's doYourThing() method attempts to open and read a file named "answer.txt" in the current directory and print its contents to the standard output. When TextFileDisplayer's doYourThing() method creates a new FileReader object, the FileReader constructor creates a new FileInputStream, whose constructor checks to see whether or not a security manager has been installed. Once again, the concrete SecurityManager has been installed, so the FileInputStream's constructor invokes checkRead() on the concrete SecurityManager. The checkRead() method instantiates a new FilePermission object representing permission to read file answer.txt and passes that object to the concrete SecurityManager's checkPermission() method, which passes the object on to the checkPermission() method of the AccessController. The AccessController's checkPermission() method performs the stack inspection to determine whether this thread should be allowed to open file answer.txt for reading. The stack appears as shown in Figure 3-8.



Figure 3-8. Stack inspection for Example2c: stops at frame three.

The call stack to be inspected in Example2c looks similar to the call stacks inspected in Example2a and Example2b. The difference is that Example2c's call stack has two extra frames: frame four, which represents the doPrivileged() invocation, and frame five, which represents the run() invocation on the PrivilegedAction object. As always, stack inspection starts at the top of the stack and proceeds on down the stack towards frame one. But once again, the inspection process will not actually reach frame one. When the AccessController reaches frame four, it discovers a doPrivileged() invocation. As a result of this discovery, the AccessController makes one more check: it checks that the code represented by frame three, the code that invoked doPrivileged(), has permission to read answer.txt. Because frame three is associated with the FRIEND protection domain, that does have permission to read question.txt, the AccessController's checkPermission() method returns normally. Because the AccessController stopped its inspection at frame three, it never considered frame two, which because it is associated with the STRANGER protection domain, doesn't have permission to read answer.txt. Thus, by invoking doPrivileged() the Friend code was able to read file answer.txt, even though code beneath it on the call stack doesn't have permission to open the file.

To get the Example2c application to work as intended, you must, as with the previous examples, start the application with an appropriate command. When using the java program from the Java 2 SDK version 1.2, the appropriate command takes the form:

java -Djava.security.manager -Djava.security.policy=policyfile.txt
-Dcom.artima.ijvm.cdrom.home=d:\books\InsideJVM\manuscript\cdrom -cp
.;jars/friend.jar;jars/stranger.jar Example2c

This command, which is contained in the ex2c.bat file in the security/ex2 directory of the CD-ROM, is an example of the kind of command you'll need to use to get the example to work. As before, to execute Example2c on your own system, you must set the com.artima.ijvm.cdrom.home property to the security/ex2 directory of your CD-ROM, or to whatever directory you may have copied the security/ex2 directory from the CD-ROM. When you run this program, it should print out the contents of answer.txt:

Complexity threatens security to a significant extent. The more
complicated a security infrastructure becomes, the more likely
parties responsible for configuring security will either make
mistakes that open up security holes or avoid using the
security infrastructure altogether.

<<  Page 15 of 17  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use