HOME > JAVA >SEMINAR

Java Security
by Bill Venners

Agenda

  • The Original Sandbox (JDK 1.0)
    • Class loaders
    • Verification
    • Security Manager
    • Language (including, type safety)
  • Code Signing (JDK 1.1)
  • Fine-Grained Access Control (JDK 1.2)

Evolution of Java Security

  • JDK 1.02:
    • Applications = Trusted
    • Applet = Untrusted
  • JDK 1.1
    • Applications, Signed Applets = Trusted
    • Unsigned Applets = Untrusted
  • JDK1.2 Spectrum
    • Fully - Partially - Untrusted Code

Sandbox

  • Class loaders
  • Verification
  • Security Manager
  • Language (including, type safety)

Class Loaders

  • Two flavors -- bootstrap and user-defined: 
  • Boostrap Class Loader -- part of JVM
  • User-Defined Class Loaders -- part of application


Name Spaces

  • Each class loader has a namespace.
  • Namespace: the set of type names already loaded.
  • Each name unique within a namespace...
  • ...but not necessarily unique across namespaces.

Duplicate Names


Names And Definitions


Why Name Spaces?

  • Deal with name conflicts.
  • Spoil plans of malicious code.

How Do Name Spaces Work?

  • Referenced types are loaded via the same class loader that loaded referencing type.

    // 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");
        }
    }
    // In file dynaext/ex1/Cat.java
    public class Cat {
        public static void main(String[] args) {
            Rodent myToy = new Mouse();
            myToy.scurry();
        }
    }
    

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

  • Used in dynamic extension
  • Represents types (front end to method area)
  • Can get information about a type
  • Can create a new instance of a 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

  • To load a type into a different name space, invoke loadClass() on a user-defined class loader. 

    // 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();
        }
    }
    // 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");
        }
    }
    // In file RodentClassLoader.java
    import java.io.*;
    import java.util.Hashtable;
    public class RodentClassLoader extends ClassLoader {
        public synchronized Class loadClass(String typeName,
            boolean resolveIt) throws ClassNotFoundException {
    
            // See if type as already been loaded by
            // this class loader
            Class result = findLoadedClass(typeName);
            if (result != null) {
                // Return an already-loaded class
                return result;
            }
    
            // Check with the primordial class loader
            try {
                result = super.findSystemClass(typeName);
                // Return a system class
                return result;
            }
            catch (ClassNotFoundException e) {
            }
    
            // Don't attempt to load a system file except
            // through the primordial class loader
            if (typeName.startsWith("java.")) {
                throw new ClassNotFoundException();
            }
    
            // Try to load it from subdirectory hole.
            byte typeData[] = getTypeFromHole(typeName);
            if (typeData == null) {
                throw new ClassNotFoundException();
            }
    
            // Parse it
            result = defineClass(typeName, typeData, 0,
                typeData.length);
            if (result == null) {
                throw new ClassFormatError();
            }
    
            if (resolveIt) {
                resolveClass(result);
            }
    
            // Return class from hole
            return result;
        }
    
        private byte[] getTypeFromHole(String typeName) {
    
            FileInputStream fis;
            String fileName = "hole" + File.separatorChar +
                typeName.replace('.', File.separatorChar)
                + ".class";
    
            try {
                fis = new FileInputStream(fileName);
            }
            catch (FileNotFoundException e) {
                return null;
            }
    
            BufferedInputStream bis =
                new BufferedInputStream(fis);
            ByteArrayOutputStream out =
                new ByteArrayOutputStream();
    
            try {
                int c = bis.read();
                while (c != -1) {
                    out.write(c);
                    c = bis.read();
                }
            }
            catch (IOException e) {
                return null;
            }
            return out.toByteArray();
        }
    }
    

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

  • JVM must verify the class files are properly formed
  • Compiler bugs, malicious class files, ...
  • Binary incompatibilities
  • Most verification done at linking time

Four Verifier Passes

  • Pass 1: When loading, verify basic class file format
    • magic number
    • attributes of proper length
    • file not too long or too short, but just right

  • Pass 2: When linking, check everything that doesn't require looking at bytecodes
    • final classes not subclassed
    • final methods not overridden
    • every class (except Object) has a subclass
    • constant pool satisfies constraints, such as each CONSTANT_Class_info has in its name_index a valid index to a CONSTANT_Utf8.
    • Checking that field and method references in the constant pool are well-formed

  • Pass 3: Also at linking, perform bytecode verification
    At any given point in the program, no matter what path is taken to reach that point:
    • The operand stack is the same size and contains the same types of values
    • No local variable is accessed unless it is known to contain a value of an appropriate type
    • Methods are invoked with the appropriate arguments
    • Fields are assigned only using values of appropriate types
    • All opcodes have appropriate argument types on the operand stack and in the local variables

  • Pass 4: Verification of symbolic references
    • Usually delayed until the symbolic reference is first used to delay loading classes

The Security Manager

  • Defined the security policy in JDK 1.0 and 1.1
  • Could only be created (1.0) or replaced (1.1) once
  • Methods of the Java API ask security manager before doing risky things
  • check() method returns quietly if OK, throws exception if not

Check Methods

  • 1.0 and 1.1 Java API calls check() methods before:
    • accept a socket connection from a specified host and port number
    • modify a thread (change its priority, stop it, etc...)
    • open a socket connection to a specified host and port number
    • create a new class loader
    • delete a specified file
    • create a new process
    • cause the application to exit
    • load a dynamic library that contains native methods
    • wait for a connection on a specified local port number
    • load a class from a specified package (used by class loaders)
    • add a new class to a specified package (used by class loaders)
    • access or modify system properties
    • access a specified system property
    • read from a specified file
    • write to a specified file

Calling check() Methods

  • In 1.0 and 1.1:
    ClassLoader loader = this.getClass().getClassLoader();
    if (loader != null) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkRead("C:\your\credit\card\numbers.txt");
        }
    }
    
  • In 1.2, call the AccessController instead

Language Features

  • Checking casts at run-time
  • Structured memory access (no pointer arithmetic)
  • Garbage collection (can't explicitly free allocated memory)
  • Array bounds checking
  • Checking references for null
  • Type safety

Type Confusion

  • Two Class Loaders: RedLoader and BlueLoader
    class Example {
    
        void f() {
            Spoofed x = Delegated.g();
        }
    }
    
    class Delegated {
    
        Spoofed g() {
            //...
        }
    }
    
  • Delegated's g() method returns an instance of a completely different class than Example is expecting:
    class Spoofed {
        public int stolen_secret_value;
        public int[] forged_pointer;
    }
    
    class Spoofed {
        private int secret_value;
        private int innocent_int;
    }
    
  • Example1 can now reveal a private field in Spoofed and forge a pointer from an int.
    class Example {
    
        void f() {
            Spoofed x = Delegated.g();
            System.out.println("secret val = "
                + x.stolen_secret_value);
            System.out.println("deref = "
                +  x.forged_pointer[0]);
        }
    }
    

New Constraints

  • In Java language, type = fully qualified name
  • In JVM, type = fully qualified name + defining class loader
  • In JVM Spec 2nd Edition, added loading constraints
  • When types are shared between name spaces, they must have the same defining loader

Beyond the Sandbox

  • Beyond the Sandbox
  • Code signing introduced in 1.1
  • Cryptography API in java.security
  • Digital signatures make it possible to authenticate who vouched for a piece of code
  • Can relax the sandbox restrictions on code signed by parties you "trust"

Code Signing

  • Generate a public/private key pair
  • Keep private key private; Make public key public
  • Place classes and other files in a JAR file
  • Tool (jarsigner in 1.2) performs a one-way hash calculation on contents to generate a "fingerprint"
  • Tool signs the hash with your private key
  • Adds the signed hash to the JAR file

Checking Signatures

  • Tool (also jarsigner in JDK 1.2) calculates one-way hash on the JAR's contents
  • Signed hash "decrypted" with signer's public key and checked with calculated hash
  • (May use certificate authorities to authenticate public keys)
  • If hashes match, you know the signer and that the code didn't change in transit

Key Distribution

  • How do you get signer's (say Fred's) public key?
  • If you know Fred, he can give it to you in person
  • If you don't know Fred, you can involve a third part called a Certificate Authority (CA)
  • Fred presents credentials and public key to CA
  • CA signs an electronic document (a "certificate") that contains Fred's public key

Using Certificates

  • You need Fred's public key or certificate before you can authenticate his JAR file
  • (Fred could include his certificate along with any doc he signs)
  • Theory: If the certificate was actually signed by the CA, the public key is actually Fred's
  • How do you get the CA's public key?
  • Why should you trust the CA?

Trust

  • Given a code signing infrastructure, you can verify who vouches for Java code
  • What do you do with that knowledge?
  • JDK1.1 - Black and White Policy
  • JDK1.2 - Shades of Grey
  • Plus: More flexible
  • Minus: More complicated

1.2 Security Parts

  • Code Source: URL source + code signers
  • Permission: permission for a particular action
  • Policy: grants permissions to code sources
  • Protection Domain: permissions for one code source
  • Access Controller: does stack inspection and says yes or no

Code Source

  • Constructor takes a URL and an array of certificates (signers)
  • URL: source of the code
  • Certificate[]: array of signers, usually obtained from a JAR file

Permission Objects

  • Permissions have 3 properties:
    • type: FilePermission, AWTPermission
    • name: "/my/finances.dat", "showWindowWithoutBannerWarning"
    • actions: "read, write"
  • Hierarchy of permissions for Java API
  • Can make your own permission classes

implies() Method

  • x.implies(y) is true if and only if
    • target (name) of x implies target of y
    • action of x implies action of y
  • Given:
    Permission file =
        new FilePermission("/tmp/f", "read");
    Permission star =
        new FilePermission("/tmp/*", "read");
    
    • star.implies(file) is true
    • file.implies(star) is false

Policy

  • One Policy object installed per application
  • Unlike SecurityManager, can replace a Policy
  • getPermissions(CodeSource) returns a collection of Permission objects
  • Policy usually defined in a policy file

Policy File

  • grant [signedBy <signer_names>]
               [, codeBase <code_source_URL>] {
    
        permission <permission_class_name>
               [<name> [, <action>]]
               [, signedBy <signer_names>];
        ...
    };
    
  • grant {
        permission java.security.AllPermission;
    };
    
  • grant signedBy "venners"
            codeBase "http://www.artima.com" {
    
        permission java.io.FilePermission
            "${user.home}${/}docs{/}-"
    };
    
    • trailing / matches all class files
    • trailing /* matches all class and JAR files
    • trailing /- matches all class and JAR files recursively

Protection Domains

  • Contains all Permissions granted to a particular CodeSource
  • Corresponds to a grant clause in a policy file
  • Class loaders assign a ProtectionDomain to every loaded class

Access Controller

  • Can't be instantiated
  • static void checkPermission(Permission)
  • Current permissions = intersection of all permissions on the current stack
  • Algo: Inspect stack from top down.
  • As soon as you hit a frame that doesn't have permission, throw AccessControllerException
A Stack Inspection

doPrivileged()

  • static Object doPrivileged(PrivilegedAction)
  • If AccessController.checkPermission() encounters a method that invoked doPrivileged(), and that method has permission, it returns
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\InsiddeJVM\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\InsiddeJVM\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)