Book Home Java Security Search this book

1.4. Running a Java Application

The parameters of the Java sandbox that we've outlined are possible elements of a Java application, but they are not required elements of an application. The remainder of this book will show us how and when those elements can be introduced into a Java application. First, however, we're going to discuss the techniques by which Java applications can be run.

There are two techniques that we'll introduce in this section: the JavaRunner technique and the Launcher technique. While both allow you to run an application securely, the examples in this chapter do not provide any security. We'll fill in the security pieces bit by bit, while we flesh out the security story. At that point, we'll show how to run Java applications securely.[2]

[2]See, for example, the end of Chapter 6, "Implementing Security Policies".

Typically, we're used to running Java applications simply by specifying on the command line the name of a class that contains a main() method. Consider this application that reads the file specified by a command-line argument:

Class Definition

public class Cat {
	public static void main(String args[]) {
		try {
			String s;
			FileReader fr = new FileReader(args[0]);
			BufferedReader br = new BufferedReader(fr);
			while ((s = br.readLine()) != null)
				System.out.println(s);
		} catch (Exception e) {
			System.out.println(e);
		}
	}
}

This is a regular Java application; if we wanted to run it and print out the contents of the password file on a Unix system, we could run the command:

Class Definition

piccolo% java Cat /etc/passwd
root:x:0:1:0000-Admin(0000):/:/usr/bin/csh
daemon:x:1:1:0000-Admin(0000):/:
bin:x:2:2:0000-Admin(0000):/usr/bin:
...

From a security point of view, this is a very rudimentary program. It contains none of the elements of the sandbox that we just listed; it has the default (wide-open) sandbox given by default to every Java application. This application can perform any operation it wants.

There are two ways in which we can add security features to this application. One way is to add to the application a class loader, a security manager, use of the access controller, and so on. This additional programming would set the bounds of the sandbox for this particular application.

The other route we can take is to run this application under the auspices of another application that we'll call JavaRunner. This is completely analogous to the way in which we typically run applets: appletviewer is a Java application that runs applets, and JavaRunner is a Java application that runs other applications. Java-Runner is responsible for establishing the parameters of the Java sandbox (that is, it ensures that appropriate class loaders, a security manager, and the like are all in place) before it invokes the target application, just as appletviewer establishes the parameters of the Java sandbox before it invokes the target applet.

This technique removes the difference (in terms of security) between an applet and an application: both types of programs are now subject to the Java sandbox. There are a number of circumstances in which this is useful:

Although the JavaRunner program is designed to run other applications, there is no reason why it cannot be modified to run applets as well. Such a modification would require some extra code to parse the HTML containing the applet tag and set up an instance of the AppletStub and AppletContext classes for the applet itself. We're not showing the code to do that only because it's not really relevant to the discussion of Java security--but the JavaRunner could easily be extended to become an appletviewer (or, with an appropriate Java bean that interprets HTML, a full-fledged browser). The advantage, of course, is that as author of the browser you would have full control over the security model the browser employs.

1.4.1. Outline of the JavaRunner Application

Here's the basic implementation of the JavaRunner application:

Class Definition

public class JavaRunner implements Runnable {
	final static int numArgs = 1;
	private Object args[];
	private String className;

	JavaRunner(String className, Object args[]) {
		this.className = className;
		this.args = args;
	}

	void invokeMain(Class clazz) {
		Class argList[] = new Class[] { String[].class };
		Method mainMethod = null;
		try {
			mainMethod = clazz.getMethod("main", argList);
		} catch (NoSuchMethodException nsme) {
			System.out.println("No main method in " + clazz.getName());
			System.exit(-1);
		}
	
		try {
			mainMethod.invoke(null, args);
		} catch (Exception e) {
			Throwable t;
			if (e instanceof InvocationTargetException)
				t = ((InvocationTargetException) e)
                                                   .getTargetException();
			else t = e;
			System.out.println("Procedure exited with exception " + t);
			t.printStackTrace();
		}
	}

	public void run() {
		Class target = null;
		try {
			target = Class.forName(className);
			invokeMain(target);
		} catch (ClassNotFoundException cnfe) {
			System.out.println("Can't load " + className);
		}
	}

	static Object[] getArgs(String args[]) {
		String passArgs[] = new String[args.length - numArgs];
		for (int i = numArgs; i < args.length; i++)
			passArgs[i - numArgs] = args[i];

		Object wrapArgs[] = new Object[1];
		wrapArgs[0] = passArgs;
		return wrapArgs;
	}

	public static void main(String args[]) {	
		if (args.length < 1) {
			System.err.println("usage:  JavaRunner classfile");
			System.exit(-1);
		}
		ThreadGroup tg = new ThreadGroup("JavaRunner Threadgroup");
		Thread t = new Thread(tg,
				new JavaRunner(args[0], getArgs(args)));
		t.start();
		try {
			t.join();
		} catch (InterruptedException ie) {
			System.out.println("Thread was interrupted");
		}
	}
}

This is a fully functional (if not full-featured) version of the JavaRunner program; we can use it to run our Cat application like this:

Class Definition

piccolo% java JavaRunner Cat /etc/passwd
root:x:0:1:0000-Admin(0000):/:/usr/bin/csh
daemon:x:1:1:0000-Admin(0000):/:
bin:x:2:2:0000-Admin(0000):/usr/bin:
...

This will give us exactly the same results as when we ran the program by hand. The invokeMain() method will use the Java reflection API to find the static main() method of the Cat class and then construct an appropriate argument list to pass to that method. Note that the use of the reflection API introduces a dependency on Java 1.1 for this program. You can write a similar program under Java 1.0, but not without using the native (C) interface to Java.

Note also that we construct a new thread group and thread, and run the main() method under control of that thread. The primary reason we do that will become clear in Chapter 6, "Implementing Security Policies" when we discuss thread security policies. But there's no reason why you couldn't expand this example to run multiple targets simultaneously, in which case each target should have its own thread and thread group anyway.

We've cheated a little bit here by using the forName() method of the Class class to find our target application class--we'll hear more about that in Chapter 3, "Java Class Loaders" when we discuss class loaders. For now, it will suffice to know that this will load our target class (assuming that the target class is found on the CLASSPATH). In addition, we still haven't done anything to set up a security manager or to enable the access controller. As a result, the sandbox for an application run under this program is non-existent: the bytecodes will not be verified, and there will be no restriction on any actions that the application may perform. But this is the example that we'll expand upon during the rest of this book as we add security features to it.

Don't think that the only function of a program like this is to run Java applications (or even Java applets). Consider the Java web server--it must dynamically invoke servlets for different web requests as those requests come in. An RMI server might operate similarly, perhaps even loading the code to perform its operations from a client machine. Although we stick with this example throughout the book, the need for security in server applications parallels the need for security in end-user applications .

1.4.2. Built-in Java Application Security

Beginning in Java 1.2, the Java platform itself comes with a security model built into applications it runs. This model is based upon information in the user's CLASSPATH. Setting the CLASSPATH is the same operation in Java 1.1 and Java 1.2, but in Java 1.2, classes that are found on the CLASSPATH may optionally be subject to a security model. This allows you to run the application code in a user- or administrator-defined sandbox: in particular, it uses the access controller of Java 1.2 to provide the same security environment for the target application as a Java-enabled browser provides for an applet.

The successful use of this facility depends upon the class loader that the built-in application runner will use, as well as depending upon the environment set up by the access controller and security manager. We'll examine how these facilities interact with this method of running applications in the next few chapters. For now, we'll just outline how this method operates.

As always, Java applications are run on the command line as follows:

Class Definition

piccolo% java Cat /etc/passwd
root:x:0:1:0000-Admin(0000):/:/usr/bin/csh
daemon:x:1:1:0000-Admin(0000):/:
bin:x:2:2:0000-Admin(0000):/usr/bin:
...

This example loads the Cat.class file from the user's CLASSPATH and runs the application with the single argument /etc/passwd. As always, when an application is run in this manner, the sandbox in which the application runs is unlimited: the application can perform any activity it wants to.

There is a very important difference between running these examples in Java 1.1 and running them in 1.2: in 1.2, classes that are loaded from the CLASSPATH will be loaded by a class loader. The addition of the class loader to the CLASSPATH allows us to build a sandbox for the application. However, none of these examples actually builds a sandbox yet. In order to build a sandbox for these examples, we must specify the -Djava.security.manager flag on the command line. This flag enables a security manager and access controller to be installed; we'll discuss the details of this option in Chapter 6, "Implementing Security Policies".

The -Djava.security.manager flag is only available in Java 1.2. Without it, Java applications in 1.2 behave exactly as they do in 1.1: they have a wide-open sandbox.

For historical reasons (and because it makes describing this facility easier), we'll refer to the ability to run applications with an optional argument to specify a sandbox as the Launcher. Given that the Launcher is a standard part of Java, you might ask why we're going to the trouble of implementing our own JavaRunner. One reason is simply to make our discussion clearer: it is easiest to understand the architecture of Java's security policy in the context of JavaRunner. Other reasons have to do with certain limitations that we'll discover about the Launcher:

Hence, both the Launcher and JavaRunner are useful mechanisms for running Java applications; which one you use depends on your particular requirements.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.