The Artima Developer Community
Sponsored Link

Java Seminars by Bill Venners
Java Security
Lecture Handout

Agenda


Evolution of Java Security


Sandbox


Class Loaders


Name Spaces


Duplicate Names


Names And Definitions


Why Name Spaces?


How Do Name Spaces Work?


The Rule in Action

// In file dynaext/ex1/Cat.java
public class Cat {
    public static void main(String[] args) {
        Rodent myToy = new Mouse();
        myToy.scurry();
    }
}



// In file dynaext/ex1/Rodent.java
public class Rodent {
    public void scurry() {
        System.out.println("Rodent scurrying");
    }
}

// In file dynaext/ex1/Mouse.java
public class Mouse extends Rodent {
    public void scurry() {
        System.out.println("Mouse scurrying");
    }
}

java.lang.Class


forName() Follows the Rule

// In file dynaext/ex2/Cat.java
public class Cat {
    public static void main(String[] args)
        throws ClassNotFoundException, IllegalAccessException,
               InstantiationException {
        Class c = Class.forName(args[0]);
        Rodent myToy = (Rodent) c.newInstance();
        myToy.scurry();
    }
}

// In file dynaext/ex2/Rodent.java
public class Rodent {
    public void scurry() {
        System.out.println("Rodent scurrying");
    }
}

// In file dynaext/ex2/Mouse.java
public class Mouse extends Rodent {
    public void scurry() {
        System.out.println("Mouse scurrying");
    }
}

loadClass() Breaks the Rule


How the Name Spaces Look

// In file dynaext/ex3/Cat.java
public class Cat {
    public static void main(String[] args)
        throws ClassNotFoundException, IllegalAccessException,
               InstantiationException {
        RodentClassLoader rcl = new RodentClassLoader();
        Class c = rcl.loadClass(args[0]);
        Rodent myToy = (Rodent) c.newInstance();
        myToy.scurry();
    }
}

Verifier


Four Verifier Passes


The Security Manager


Check Methods


Calling check() Methods


Language Features


Type Confusion


New Constraints


Beyond the Sandbox


Code Signing


Checking Signatures


Key Distribution


Using Certificates


Trust


1.2 Security Parts


Code Source


Permission Objects


implies() Method


Policy


Policy File


Protection Domains


Access Controller

A Stack Inspection

doPrivileged()

A Stack Inspection with doPrivileged()

Exercises

Problem 1

In the Security/ex2 directory, create two JAR files, friend.jar and stranger.jar, which will contain the class files for Friend.java and Stranger.java. For an explanation of this process, here's an excerpt from Inside the Java Virtual Machine:

For an example of code signing with the jarsigner tool of the Java 2 SDK 1.2, consider the following types, Doer, Friend, and Stranger. The first type, Doer, defines an interface that the other two types, classes Friend and Stranger implement:

// On CD-ROM in file
// security/ex2/com/artima/security/doer/Doer.java
package com.artima.security.doer;

public interface Doer {

    void doYourThing();
}

Doer declares just one method, doYourThing(). Class Friend and class Stranger implement this method in the exact same way. In fact, besides their names, the two classes are identical:

// 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;
                    }
                }
            );
        }
    }
}

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

public class Stranger implements Doer {

    private Doer next;
    private boolean direct;

    public Stranger(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;
                    }
                }
            );
        }
    }
}

These types -- Doer, Friend, and Stranger -- are designed to illustrate the stack inspection mechanism of the access controller. The motivation behind their design will be made clear later in this chapter, when several examples of stack inspection are given. At this point, however, the class files generated by compiling Friend and Stranger must be signed to prepare them for the upcoming stack inspection examples. The class files generated from Friend.java will be signed by a party referred to fondly as "friend." The class files generated from Stranger.java will be signed by a party referred to somewhat suspiciously as "stranger." The class file generated by Doer will not be signed.

To prepare the class files for signing, they must first be placed into JAR files. Because the class files for Friend and Stranger need to be signed by two different parties, they will be collected into two different JAR files. The two class files generated by compiling Friend.java, Friend.class and Friend$1.class, will be placed into a JAR file called friend.jar. Similarly, the two class files generated by compiling Stranger.java, Stranger.class and Stranger$1.class, will be placed into a JAR file called stranger.jar.

Friend.java's class files are dropped by the javac compiler in the security/ex2/com/artima/security/friend directory. Because class Friend is declared in the com.artima.security.friend package, Friend.java's class files must be placed in the JAR file in the com/artima/security/friend directory. The following command, executed in the security/ex2 directory, will place Friend.class and Friend$1.class into a newly created JAR file called friend.jar, which is placed in the current directory, security/ex2:

jar cvf friend.jar com/artima/security/friend/*.class

Once the previous command completes, the class files for Friend.java must be removed so they won't be found by the Java virtual machine when it runs the access control examples:

rm com/artima/security/friend/Friend.class
rm com/artima/security/friend/Friend$1.class

Filling a JAR file with Stranger.java's class files, which are dropped by javac in the security/ex2/com/artima/security/stranger directory, requires a similar process. From the security/ex2 directory, the following command must be executed:

jar cvf stranger.jar com/artima/security/stranger/*.class
rm com/artima/security/stranger/Stranger.class
rm com/artima/security/stranger/Stranger$1.class

Problem 2

Create a public/private key pair for both friend and stranger. For an explanation of this process, here's another excerpt from Inside the Java Virtual Machine:

To sign a JAR file with the jarsigner tool from the Java 2 SDK 1.2, a public/private key pair for the signer must already exist in a keystore file, which is a file for storing named, password-protected keys. The keytool program of the Java 2 SDK 1.2, can be used to generate a new key pair, associate the key pair with a name or alias, and protect it with a password. The alias, which is unique within each keystore file, is used to identify a particular key pair in a particular keystore file. The password for a key pair is required to access or change the information contained in the keystore file for that key pair.

The access control examples expect a keystore file named ivjmkeys in the security/ex2 directory containing key pairs for the aliases "friend" and "stranger." The following command, executed from the security/ex2 directory, will generate the key pair for the alias, friend, with the password, friend4life. In the process, it will create the keystore file named ijvmkeys:

keytool -genkey -alias friend -keypass friend4life -validity 10000 -
keystore ijvmkeys

The -validity 10000 command line argument of the previous keytool command indicates that the key pair should be valid for 10000 days, which at over 27 years, is likely enough time to outlive the product lifecycle of this edition of this book. When the command runs, it will prompt for a keystore password, which is a general password required for any kind of access or change of the keystore file. The keystore password given to ijvmkeys is "ijvm2ed".

The key pair for stranger can be generated with a similar command:

keytool -genkey -alias stranger -keypass stranger4life -validity 10000 -
keystore ijvmkeys

Problem 3

Sign the JAR files. For an explanation of this process, here's yet another excerpt from Inside the Java Virtual Machine:

Now that the keystore file ijvmkeys contains key pairs for friend and stranger, and the JAR files friend.jar and stranger.jar contain the appropriate class files, the JAR files can finally be signed. The following jarsigner command, executed from the examples/ex2 directory, will sign the class files contained in friend.jar using friend's private key:

jarsigner -keystore ijvmkeys -storepass ijvm2ed -keypass friend4life
friend.jar friend

A similar command will sign the class files contained in stranger.jar with stranger's private key:

jarsigner -keystore ijvmkeys -storepass ijvm2ed -keypass stranger4life
stranger.jar stranger

Whew, that was a lot of work just to sign two JAR files. And keep in mind that in the real world, you'd have to make sure no one with bad intent got a hold of your private keys, and that you kept track of them. That means not losing the keystore file, remembering the passwords, and so on. In addition, you'll have to get your public keys to anyone who is going to use your signature to give your code access to their system.

Problem 4

Using the policy file policyfile.txt in the Security/ex2 directory, perform a stack inspection that says "Yes," as described in this excerpt from Inside the Java Virtual Machine:

As the first stack inspection example, consider the Example2a application:

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

// This succeeds because everyone has permission to
// read answer.txt
class Example2a {

    public static void main(String[] args) {

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

        Friend friend = new Friend(tfd, true);

        Stranger stranger = new Stranger(friend, true);

        stranger.doYourThing();
    }
}

The Example2a application creates three Doer objects: a TextFileDisplayer, a Stranger, and a Friend. The TextFileDisplayer constructor is passed the String, "question.txt". When its doYourThing() method is invoked, it will attempt to open a file named question.txt in the current directory for reading and print its contents to the standard output. The Friend object's constructor is passed a reference to the TextFileDisplayer object (a Doer) and the boolean value true. Because the passed boolean value is true, when Friend's doYourThing() method is invoked, it will directly invoke doYourThing() on the TextFileDisplayer object. The Stranger object's constructor is passed a reference to the Friend object (also a Doer) and the boolean value true. Because the passed boolean value is true, when Stranger's doYourThing() method is invoked, it will directly invoke doYourThing() on the Friend object. After creating these three Doer objects, and hooking them together as described, Example2a's main() method invokes doYourThing() on the Stranger object and the fun begins.

When the Example2a program invokes doYourThing() on the Stranger object referenced from the stranger variable, the Stranger object invokes doYourThing() on the Friend object, which invokes doYourThing() on the TextFileDisplayer object. TextFileDisplayer's doYourThing() method attempts to open and read a file named "question.txt" in the current directory (the directory in which the Example2a application was started) and print its contents to the standard output. When TextFileDisplayer's doYourThing() method creates a new FileReader object, the FileReader's constructor creates a new FileInputStream, whose constructor checks to see whether or not a security manager has been installed. In this case, 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 question.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 question.txt for reading.

Figure 3-6 shows the call stack when the AccessController's checkPermission() method is invoked. In this figure, each frame of the call stack is represented by a horizontal row that is composed of several elements. The leftmost element in each stack frame row, which is labeled "class," is the fully qualified name of the class in which the method represented by that stack frame is defined. The next element to the right, which is labeled "method," gives the name of the method. The next element, which is labeled "protection domain," indicates the protection domain with which each frame is associated. Farthest to the right is an arrow that shows the progression of the AccessController's checkPermission() method as it checks whether each stack frame has permission to perform the requested action. Just to the left of the arrow is a number for each stack frame. Like all images of the stack shown in this book, the top of the stack appears at the bottom of the picture. Thus, in Figure 3-6, the top of the stack is the frame numbered 10.



Figure 3-6. Stack inspection for Example2a: all frames have permission.

The protection domain column of the stack diagram shown in Figure 3-6 shows each frame associated with one of four protection domains, named "FRIEND," "STRANGER," "CD-ROM," and "BOOTSTRAP." Three of these protection domains correspond to grant clauses in policyfile.txt. The FRIEND protection domain corresponds to the grant clause that gives permission to any code signed by friend to read question.txt and answer.txt. The STRANGER protection domain corresponds to the grant clause that gives permission to any code signed by stranger to read question.txt. The CD-ROM protection domain corresponds to the grant clause that gives permission to any code loaded from the "${com.artima.ijvm.cdrom.home}/security/ex2/" directory to read question.txt and answer.txt. The fourth and final protection domain, named BOOTSTRAP, doesn't correspond to any grant clause in policyfile.txt. Rather, the BOOTSTRAP protection domain represents the permissions granted to any code loaded by the bootstrap class loader, which is responsible for loading the class files of the Java API. Code in the BOOTSTRAP protection domain is granted java.lang.AllPermission, which gives it permission to do anything.

To get the Example2a application to demonstrate stack inspection as intended, you must 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 Example2a

This command, which is contained in the ex2a.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. By defining the java.security.manager property on the command line, you indicate you want the concrete SecurityManager to be automatically installed. Because the Example2a application doesn't install a security manager explicitly, if you neglect to define the java.security.manager property on the command line, no security manager will be installed and the code will be able do anything. The -cp argument sets up the class path, which causes the virtual machine to look for class files in the current directory and in the friend.jar and stranger.jar files in the jars subdirectory. The com.artima.ijvm.cdrom.home property indicates the directory in which Doer, Example2a, and TextFileDisplayer are located. This property is used by the third grant clause in policyfile.txt, which corresponds to the protection domain named "CD-ROM." As a result, types Doer, Example2a, and TextFileDisplayer will be loaded into the CD-ROM protection domain and granted permission to read to both question.txt and answer.txt. To execute Example2a 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 the AccessController performs its stack inspection, it starts at the top of the stack, frame ten, and heads down to frame one, which is the frame for the first method invoked by this thread, main() of class Example2a. In the case of the Example2a application, every frame on the call stack has permission to perform the action: to read the file "question.txt". This is because all four protection domains represented on the call stack -- FRIEND, STRANGER, CD-ROM, and BOOTSTRAP -- include or imply a FilePermission for reading question.txt in the current directory. When the AccessController's checkPermission() method reaches the bottom of the stack without having encountered any frames that don't have permission to read the file, it returns normally, without throwing an exception. The FileInputStream goes ahead and opens the file for reading. The Example2a application reads in the contents of question.txt and prints them to the standard output, which looks like this:

Too what extent does complexity threaten security?

Problem 5

Using the policy file policyfile.txt in the Security/ex2 directory, perform a stack inspection that says "No," as described in this excerpt from Inside the Java Virtual Machine:

As an example of a stack inspection that results in a denied permission, consider the Example2b application from the security/ex2 directory of the CD-ROM:

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

// This fails because the Stranger code doesn't have
// permission to read file question.txt

class Example2b {

    public static void main(String[] args) {

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

        Friend friend = new Friend(tfd, true);

        Stranger stranger = new Stranger(friend, true);

        stranger.doYourThing();
    }
}

The only difference between Example2b and the previous example, Example2a, is that whereas Example2a passes the file name "question.txt" to the TextFileDisplayer constructor, Example2b passes the file name "answer.txt". This small change to the application makes a big difference to the outcome of the program, however, because one of the methods on the stack doesn't have permission to access "answer.txt".

When the Example2b program invokes doYourThing() on the Stranger object referenced from the stranger variable, the Stranger object invokes doYourThing() on the Friend object, which invokes doYourThing() on the TextFileDisplayer object. TextFileDisplayer's doYourThing() method attempts to open and read a file named "answer.txt" in the current directory (the directory in which the Example2b application was started) 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. In this case, 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 call stack to be inspected in Example2b, which is shown in Figure 3-7, looks identical to the call stack that was inspected in Example2a. The only difference is that this time, rather than making sure every frame on the stack has permission to read file question.txt, the AccessController will make sure every frame on the stack has permission to read answer.txt. As always, stack inspection starts at the top of the stack and proceeds on down the stack towards frame one. But this time, the inspection process never actually reaches frame one. When the AccessController reaches frame two, it discovers that the code of the Stranger class, to whom the doYourThing() method of frame two belongs, doesn't have permission to read "answer.txt". Because all frames of the stack must have permission, the stack inspection process need go no farther than frame two. The AccessController's checkPermission() method throws an AccessControl exception.



Figure 3-7. Stack inspection for Example2b: frame two doesn't have permission.

To get the Example2b application to work as intended, you must 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 Example2b

This command, which is contained in the ex2b.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 Example2b 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, you should see this output:

Exception in thread "main" java.security.AccessControlException: access
denied (java.io.FilePermission answer.txt read)
	at java.security.AccessControlContext.checkPermission(AccessControlContext.java:195)
	at java.security.AccessController.checkPermission(AccessController.java:403)
	at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
	at java.lang.SecurityManager.checkRead(SecurityManager.java:873)
	at java.io.FileInputStream.(FileInputStream.java:65)
	at java.io.FileReader.(FileReader.java:35)
	at TextFileDisplayer.doYourThing(TextFileDisplayer.java, Compiled Code)
	at com.artima.security.friend.Friend.doYourThing(Friend.java:21)
	at com.artima.security.stranger.Stranger.doYourThing(Stranger.java:21)
	at Example2b.main(Example2b.java:18)

Problem 6

Using the policy file policyfile.txt in the Security/ex2 directory, perform a stack inspection that makes use of the doPrivileged() method, as described in this excerpt from Inside the Java Virtual Machine:

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.

Problem 7

Using the policy file policyfile.txt in the Security/ex2 directory, perform a stack inspection that demonstrates a futile use of the doPrivileged() method, as described in this excerpt from Inside the Java Virtual Machine:

It is important to understand that a method can never grant itself more privileges than it already has with a doPrivileged() invocation. By calling doPrivileged(), a method is merely enabling privileges it already has. It is telling the AccessController that it is taking responsibility for exercising its own permissions, and that the AccessController should ignore the permissions of its callers. Thus, the doPrivileged() call in the previous example, Example2c enabled answer.txt to be read because Friend, the class that executed the doPrivileged(), already had permission to read the file, and so did all the frames above it on the stack.

For an example of a futile attempt to use doPrivileged(), consider the Example2d application from the security/ex2 directory of the CD-ROM:

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

// This fails because even though Stranger does
// a doPrivileged() call, Stranger doesn't have
// permission to read question.txt. (Passing
// false as second arg to Stranger constructor
// causes it to do a doPrivileged().)

class Example2d {

    public static void main(String[] args) {

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

        Stranger stranger = new Stranger(tfd, false);

        Friend friend = new Friend(stranger, true);

        friend.doYourThing();
    }
}

The difference between Example2d and the previous example, Example2c, is that the Stranger and Friend objects have swapped positions and roles. The Stranger object is now farther up the stack, with the Friend below it on the stack. And this time, it is Stranger that will make the call to doPrivileged(), not Friend.

When the Example2d program invokes doYourThing() on the Friend object referenced from the friend variable, the Friend object invokes doYourThing() on the Stranger 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 two examples, 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. As in all the examples, 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 presented to the AccessController by Example2d is shown in Figure 3-9.



Figure 3-9. Stack inspection for Example2d: frame five doesn't have permission.

The call stack to be inspected in Example2d looks similar to the call stack inspected in Example2c. The only difference is that Friend and Stranger have swapped positions. As always, stack inspection starts at the top of the stack and proceeds on down the stack towards frame one. But alas, once again the inspection process will not actually reach frame one. When the AccessController reaches frame five, it discovers a stack frame associated with the STRANGER protection domain, which doesn't have permission to read answer.txt. As a result of this discovery, the AccessController throws an AccessControlException, indicating the requested read of answer.txt should not be performed.

Had the Stranger class been able to enlist the assistance of an instance of some class that implemented PrivilegedAction, performed the desired invocation of the TextFileDisplayer's doYourThing() method, and belonged to a protection domain that has permission to read >answer.txt, Stranger's attempt to open answer.txt with the help of doPrivileged() would have still been futile. Imagine, for example, that the code of the run() method represented by frame five of Example2d's call stack had been associated with to the CD-ROM protection domain. In that case, the AccessController would have determined that frame five had permission to open answer.txt and continued on to frame four. At frame four, the AccessController would have discovered the doPrivileged() invocation. As a result of this discovery, the AccessController would make one more check: it would make certain the method that invoked doPrivileged(), which in this case was Stranger's doYourThing() method represented by stack frame three, has permission to read file answer.txt. Because frame three is associated with the STRANGER protection domain that doesn't have permission to read answer.txt, the AccessController would still throw an AccessControlException.

To get the Example2d application to work as intended, you must start the application with yet another 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 Example2d

This command, which is contained in the ex2d.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 Example2d 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, you should see the kind of output that crackers everywhere hate to see:

Exception in thread "main" java.security.AccessControlException: access
denied (java.io.FilePermission answer.txt read)
	at java.security.AccessControlContext.checkPermission(AccessControlContext.java:195)
	at java.security.AccessController.checkPermission(AccessController.java:403)
	at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
	at java.lang.SecurityManager.checkRead(SecurityManager.java:873)
	at java.io.FileInputStream.(FileInputStream.java:65)
	at java.io.FileReader.(FileReader.java:35)
	at TextFileDisplayer.doYourThing(TextFileDisplayer.java, Compiled Code)
	at com.artima.security.stranger.Stranger$1.run(Stranger.java:27)
	at java.security.AccessController.doPrivileged(Native Method)
	at com.artima.security.stranger.Stranger.doYourThing(Stranger.java:24)
	at com.artima.security.friend.Friend.doYourThing(Friend.java:21)
	at Example2d.main(Example2d.java:21)

Sponsored Links

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