Return-Path: <nobody@hermes.java.sun.com>
Received: from fort-point-station.mit.edu by po10.mit.edu (8.9.2/4.7) id KAA29098; Wed, 29 Nov 2000 10:15:19 -0500 (EST)
Received: from hermes.java.sun.com (hermes.java.sun.com [204.160.241.85])
	by fort-point-station.mit.edu (8.9.2/8.9.2) with ESMTP id KAA25083;
	Wed, 29 Nov 2000 10:12:00 -0500 (EST)
Received: (from nobody@localhost)
	by hermes.java.sun.com (8.9.3+Sun/8.9.1) id PAA12771;
	Wed, 29 Nov 2000 15:09:43 GMT
Date: Wed, 29 Nov 2000 15:09:43 GMT
Message-Id: <200011291509.PAA12771@hermes.java.sun.com>
X-Mailing: 307
From: JDCTechTips@sun.com
Subject: JDC Tech Tips  November 28, 2000
To: JDCMember@sun.com
Reply-To: JDCTechTips@sun.com
Errors-To: bounced_mail@hermes.java.sun.com
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
X-Mailer: Beyond Email 2.2


 J  D  C    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, 
November 28, 2000. This issue covers:
 
         * Using Privileged Scopes 
         * Debugging Class Loading
         
Underlying both tips is the use of the boot class path. In the 
first tip, you use the boot class path and privileged scopes to 
add a simple logging feature to the Java(tm) security 
architecture. In the second tip, you use the boot class path 
and other techniques to debug class loading.         
                
This tip was developed using Java(tm) 2 SDK, Standard Edition, 
v 1.3.

This issue of the JDC Tech Tips is written by Stuart Halloway,
a Java specialist at DevelopMentor (http://www.develop.com/java).

You can view this issue of the Tech Tips on the Web at
http://java.sun.com/jdc/TechTips/2000/tt1128.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
USING PRIVILEGED SCOPES 

The core Java security architecture is based on granting 
permissions to code based on where the code is located. In the 
Java(tm) 2 SDK, Standard Edition, starting with version 1.2, 
these permissions are configured by editing a text file called 
the policy file. For example, if you wanted to grant permission 
to read and write files in a "temp" subdirectory off the root, 
you might use a policy file like this:

//file my.policy
grant {
  permission java.io.FilePermission "${/}temp${/}-", "read,write";
};

The grant block begins by specifying the location of the code that 
should be granted permissions. If there were such a specification 
in the example, it would go before the ${/}. But there is no
location specified. So the grant applies to all code. Inside the 
braces are a list of permissions. In the example, the 
FilePermission syntax gives permission to read and write files in 
the temp subdirectory and all its subdirectories. The special ${/} 
syntax will be replaced by the path separator on the local platform.

To verify that this policy file works correctly, compile and
execute the following java class:

import java.io.*;
public class TestPolicy {
  public static void main(String [] args) {
    tryToRead("/temp/foo.txt");
    tryToRead("/qwyjibo/foo.txt");
  }
  public static void tryToRead(String fileName) {
    try {
      FileInputStream fis = new FileInputStream(fileName);
    }
    catch (SecurityException se) {
      System.out.println("Didn't have permission to read " + fileName);
      se.printStackTrace();
      return;
    }
    catch (Exception e) {
      //don't really care if the file was there, just checking
      //if we would have been allowed to read it
    }
    System.out.println("Granted permission to read " + fileName);
  }
}

To execute the program with security active and referencing your
policy file, you will need to use the command line

  java -Djava.security.manager -Djava.security.policy=my.policy TestPolicy

If everything works as expected, you should see output similar to
this:

  Granted permission to read /temp/foo.txt
  Didn't have permission to read /qwyjibo/foo.txt
  java.security.AccessControlException: access denied (java.io.FilePermission qwyjibo/foo.txt read)
        at java.security.AccessControlContext.checkPermission(Unknown Source)
        at java.security.AccessController.checkPermission(Unknown Source)
        at java.lang.SecurityManager.checkPermission(Unknown Source)
        at java.lang.SecurityManager.checkRead(Unknown Source)
        at java.io.FileInputStream.<init>(Unknown Source)
        at TestPolicy.tryToRead(TestPolicy.java:10)
        at TestPolicy.main(TestPolicy.java:6)

Permission checks work by checking the entire call stack. Every 
class on the call stack must have the requisite permission, or 
the security check fails. This is based on the assumption that 
the security manager has no special knowledge of your code, and 
has to assume that any untrusted code, anywhere on the stack, 
might be a threat. In the exception output above, all of the 
classes that begin with "java" are part of the core API and pass 
all security checks. The only problem is the TestPolicy class, 
which does not have permission to access files in the "/qwyjibo" 
directory.

Now, imagine that you wanted to keep an audit log of all failed 
file reads. To do this, you might extend the normal 
SecurityManager as follows:

//ATTENTION: compile this into a subdirectory named 'boot'
import java.io.*;
import java.security.*;
public class LoggingSM extends SecurityManager {
  public void checkRead(String name) {
    try {
      super.checkRead(name);
    }
    catch(SecurityException se) {
      log(name, se);
      throw se;
    }
  }
  public void log(String name, Exception se) {
    try {
      FileOutputStream fos = new FileOutputStream("security.log");
      PrintStream ps = new PrintStream(fos);
      ps.println("failed attempt to read " + name);
      se.printStackTrace(ps);
    }
    catch (Exception e) {
      System.out.println("uh-oh, the log is busted somehow");
      e.printStackTrace();
    }
  }
}

This subclass of SecurityManager calls the default 
implementation's checkRead method, but catches the exception and
logs it before throwing it back to the client.

As the comment in LoggingSM states, compile the class into a 
subdirectory named "boot." After you compile the class, you can use 
it as your SecurityManager by specifying its name on the command 
line like this:

  java -Xbootclasspath/a:boot/ -Djava.security.manager=LoggingSM\
       -Djava.security.policy=my.policy TestPolicy

The addition of the -Xbootclasspath/a: flag appends the "boot"
subdirectory to the bootstrap class path. This causes the LoggingSM 
class to be loaded by the bootstrap class loader, so that the 
class will not fail security checks. When you run this command, 
you would like to see the failed file read appear in the 
security.log file. Unfortunately, this doesn't happen. Instead, 
you get a console report that notes the expected security failure,
and indicates that the log failed to work. You should see something
similar to this:

  uh-oh, the log is busted somehow
  java.security.AccessControlException: access denied (java.io.FilePermission security.log write)
        at java.security.AccessControlContext.checkPermission(Unknown Source)
        at java.security.AccessController.checkPermission(Unknown Source)
        at java.lang.SecurityManager.checkPermission(Unknown Source)
        at java.lang.SecurityManager.checkWrite(Unknown Source)
        at java.io.FileOutputStream.<init>(Unknown Source)
        at java.io.FileOutputStream.<init>(Unknown Source)
        at LoggingSM.log(LoggingSM.java:16)
        at LoggingSM.checkRead(LoggingSM.java:10)
        at java.io.FileInputStream.<init>(Unknown Source)
        at TestPolicy.tryToRead(TestPolicy.java:9)
        at TestPolicy.main(TestPolicy.java:5)

The call stack clearly illustrates the problem. Because the
untrusted TestPolicy class was on the call stack, the attempt to
open the FileInputStream throws a SecurityException.  But, when 
the LoggingSM class attempts to write to the log, the mischievous 
TestPolicy class is still on the stack. So, the SecurityManager 
blindly rejects the attempt to write the log. What this situation 
calls for is some way for LoggingSM to insist "I know what I am 
doing when I open the log file, so there is no need to check the 
call stack any further."

The AccessController.doPrivileged() method neatly solves the 
problem. When you place a block of code inside a doPrivileged 
method, you are asserting that, based on your knowledge of the 
code, you are confident that it is safe for the operation to 
proceed without any additional security checks. Note that you are
not turning off security entirely -- the code that calls the 
AccessController must still pass its own security check. (This is 
why you added LoggingSM to the boot class path.) To fix the log so
that it uses a privileged block, replace the log method as 
follows:

  private void log(String name, Exception se) {
    try {
      FileOutputStream fos = (FileOutputStream) 
        AccessController.doPrivileged(new PrivilegedExceptionAction() {
          public Object run() throws PrivilegedActionException {
            try {
              return new FileOutputStream("security.log");
                } catch (IOException ioe) {
                throw new PrivilegedActionException(ioe);
            }
          }
        });
      PrintStream ps = new PrintStream(fos);
      ps.println("failed attempt to read " + name);
      se.printStackTrace(ps);
    }
    catch (Exception e) {
      System.out.println("uh-oh, the log is busted somehow");
      e.printStackTrace();
    }
  }

The doPrivileged method executes the run method of the anonymous 
inner subclass of PrivilegedExceptionAction. When a security check 
is necessary, it stops walking back up the call stack after it hits 
this block of code.  

Recompile LoggingSM into the "boot" subdirectory.
Now you can run the application with the command line 

  java -Djava.security.manager=LoggingSM -Djava.security.policy=my.policy\
       -Xbootclasspath/a:boot/ TestPolicy
       
This time, the LoggingSM will be able to write to the file 
system, so after the program runs the security.log file will 
have a correct report of security failures that occurred. If you 
have trouble getting the example to work, try adding the
"-Djava.security.debug=all" flag on the command line. This flag
produces exhaustive trace output of the security system.

For more information about privileged scopes, see "API for 
Privileged Blocks" at
http://java.sun.com/j2se/1.3/docs/guide/security/doprivileged.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
DEBUGGING CLASS LOADING

The October 31, 2000 edition of the Tech Tips included a quick 
overview of the ClassLoader architecture. Unfortunately, even 
after you have a good understanding of the architecture it is 
easy to get lost when debugging a complex system with multiple 
class loaders. This tip will help you troubleshoot common 
class loading problems. Begin by compiling these classes:

public class LoadMe {
  static {
    System.out.println("Yahoo!  I got loaded");
  }
}

import java.net.*;
public class Loader {
  public static void main(String [] args) throws Exception
  {
    URLClassLoader uclMars = new URLClassLoader(new URL[]{new URL("file:mars/")});
    URLClassLoader uclVenus = new URLClassLoader(new URL[]{new URL("file:venus/")});
    Class mars = Class.forName("LoadMe", true, uclMars);
    Class venus = Class.forName("LoadMe", true, uclVenus);
    System.out.println("(Venus version == mars version) == " + (mars == venus));
  }
}

Before running the Loader class, create three copies of the 
compiled LoadMe.class file: one in the same directory as Loader, 
one in a "mars" subdirectory, and one in a "venus" subdirectory. 
The objective of this test is to load two different versions 
of the same class. (Before reading further, see if you can 
determine why this isn't going to work.) When you run the Loader 
class, you will see the following output: 

  Yahoo!  I got loaded
  (Venus version == mars version) == true

Contrary to the plan, the mars and venus versions of the class 
are the same. A first step to debugging this is to use the 
-verbose:class flag on the command line:

  java -verbose:class Loader
  [Opened E:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
  [Opened E:\Program Files\JavaSoft\JRE\1.3\lib\i18n.jar]
  [Opened E:\Program Files\JavaSoft\JRE\1.3\lib\sunrsasign.jar]
  [Loaded java.lang.Object from E:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
  ...
  [Loaded Loader]
  [Loaded LoadMe]

You should see several screens of output listing all the classes 
as the VM* loads them. For classes loaded by the bootstrap class 
loader, this output will show you exactly what JAR file the class 
came from. This information alone should quickly resolve many 
class loader problems. For example, it would help you identify
the fact that you are accidentally running with your JAVA_HOME
environment variable pointing to another installed copy of the 
Java(tm) platform. Unfortunately, the output does not contain 
enough information to solve the LoadMe problem. Although the 
output clearly shows that only one copy of the LoadMe class was 
loaded, it does not show where the class came from.

To get even more information, you can install a version of
URLClassLoader that logs every class. In order to do this, you 
need to recompile java.net.URLClassLoader, and then order the VM 
to use your "hacked" version. (Using a hacked version of a core
API class should be used for debugging purposes only and to
explore the VM.)

Here is a replacement of URLClassLoader with logging added:

  //extract java.net.URLClassLoader from src.jar in your JDK directory
  //to a "boot" subdirectory.  Insert the following method and recompile
  protected Class loadClass(String name, boolean resolve) 
    throws ClassNotFoundException
  {
    Class cls = null;
    try {
      cls = super.loadClass(name, resolve);
      return cls;
    }
    finally {
      System.out.print("Class " + name);
      if (cls == null) {
        System.out.println(" could not be loaded by " + this);
      } else {
        ClassLoader cl = cls.getClassLoader();
        if (cl == this) {
          System.out.println(" loaded by " + cl);
        } else {
          System.out.println(" requested by " + this + ", loaded by " + cl);
        }
      }
    }
  }

Notice the comment in the URLClassLoader replacement. First 
extract java.net.URLClassLoader from src.jar in your JDK directory
to a "boot" subdirectory. Insert into it the loadClass method.
Then recompile URLClassLoader.

Notice that the logging method is explicit about class loader
delegation. If one class loader is asked for a class, but its 
parent class loader returns the class first, the output reports 
both class loaders.  

Now, you can use the "prepend" version of the bootclasspath
flag to force this version of URLClassLoader to be loaded instead 
of the normal one:
  
  java -Xbootclasspath/p:boot/ Loader

If you search the console output for the string "LoadMe" you 
should find something like this:

  Class LoadMe loaded by sun.misc.Launcher$AppClassLoader@404536
  Class LoadMe requested by java.net.URLClassLoader@5d87b2, \
               loaded by sun.misc.Launcher$AppClassLoader@404536

This output immediately identifies the problem. The LoadMe class 
is not loaded by the URLClassLoader because it is already visible
on the CLASSPATH; it is represented here by a member class of
sun.misc.Launcher. To fix the bug, remove the copy of the LoadMe 
class from the main project directory.

This is only one example of how you can use a custom version of 
a core API class to aid debugging. You can use the boot class path 
anywhere you need to inject debugging code into the core API. 
But you need to understand exactly what you are doing -- a 
defective version of a core API class can compromise the entire 
VM. Also, the the license forbids shipping a modified core class.
As mentioned earlier, you should use this technique only to 
debug applications and explore the VM, never to ship code to 
a customer.

For more on using the bootclasspath, see the white paper
"Using the BootClasspath" by Ted Neward at 
http://www.javageeks.com/Papers/BootClasspath/

.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

- NOTE

Sun respects your online time and privacy. The Java Developer 
Connection mailing lists are used for internal Sun Microsystems(tm) 
purposes only. You have received this email because you elected 
to subscribe. To unsubscribe, go to the Subscriptions page 
(http://developer.java.sun.com/subscription/), uncheck the 
appropriate checkbox, and click the Update button.


- SUBSCRIBE

To subscribe to a JDC newsletter mailing list, go to the 
Subscriptions page (http://developer.java.sun.com/subscription/), 
choose the newsletters you want to subscribe to, and click Update.


- FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:

jdc-webmaster@sun.com


- ARCHIVES
You'll find the JDC Tech Tips archives at:

http://java.sun.com/jdc/TechTips/index.html


- COPYRIGHT
Copyright 2000 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This document is protected by copyright. For more information, see:

http://java.sun.com/jdc/copyright.html


JDC Tech Tips 
November 28, 2000


* As used in this document, the terms "Java virtual machine" 
  or "JVM" mean a virtual machine for the Java platform.





