[ Team LiB ] Previous Section Next Section

7.2 Loading Untrusted Code

Let's continue our Server example. Suppose now that you want to modify the server so that it can load Service classes over the network from an arbitrary URL. Suppose also that you want to give Service classes the ability to read and write files from a "scratch" directory on the local system. You can accomplish this by writing a simple class that uses URLClassLoader to load service classes and pass them to an instance of the Server class. To make it work, however, you also have to develop an appropriate security policy file.

Example 7-1 shows our SafeServer class. Like the original Server class, this one expects a list of Service classes and port numbers on the command line. But the first command-line argument it expects is the URL from which the service classes should be downloaded.

Example 7-1. SafeServer.java
package je3.security;
import je3.net.Server;
import java.io.*;
import java.net.*;
import java.security.*;

 * This class is a program that uses the Server class defined in Chapter 5.
 * Server would load arbitrary "Service" classes to provide services.
 * This class is an alternative program to start up a Server in a similar 
 * way.  The difference is that this one uses a SecurityManager and a 
 * ClassLoader to prevent the Service classes from doing anything damaging
 * or malicious on the local system.  This allows us to safely run Service 
 * classes that come from untrusted sources.
public class SafeServer {
    public static void main(String[  ] args) {
        try {
            // Install a Security manager if the user didn't already install
            // one with the -Djava.security.manager argument
            if (System.getSecurityManager( ) == null) {
                System.out.println("Establishing a security manager");
                System.setSecurityManager(new SecurityManager( ));

            // Create a Server object
            Server server = new Server(null, 5);

            // Create the ClassLoader that we'll use to load Service classes.
            // The classes should be stored in the JAR file or the directory
            // specified as a URL by the first command-line argument
            URL serviceURL = new URL(args[0]);
            ClassLoader loader =
                new java.net.URLClassLoader(new URL[  ] {serviceURL});

            // Parse the argument list, which should contain Service name/port
            // pairs.  For each pair, load the named Service using the class
            // loader, then instantiate it with newInstance( ), then tell the
            // server to start running it.
            int i = 1;
            while(i < args.length) {
                // Dynamically load the Service class using the class loader
                Class serviceClass = loader.loadClass(args[i++]);      
                // Dynamically instantiate the class.  
                Server.Service service =
                    (Server.Service)serviceClass.newInstance( );
                int port = Integer.parseInt(args[i++]);  // Parse the port #
                server.addService(service, port);        // Run service
        catch (Exception e) { // Display a message if anything goes wrong
            System.err.println("Usage: java " + SafeServer.class.getName( ) +
                               " <url> <servicename> <port>\n" +
                               "\t[<servicename> <port> ... ]");

7.2.1 A Policy for SafeServer

The SafeServer class creates and establishes a SecurityManager even if the user doesn't do this with the -Djava.security.manager argument. This means that the program is not able to run without a security policy that grants it the permissions it needs. Example 7-2 shows a policy file you can use to make it work.

There are a couple of things to note about the SafeServer.policy file. First, the policy file reads system properties named service.dir and service.tmp. These are not standard system properties; they are properties that you must specify to the Java interpreter when you run the SafeServer program. service.dir specifies the directory from which the service classes are to be loaded. Assume here that they are loaded via a local file: URL, not through an http: or other network URL. The service.tmp property specifies a directory in which the service classes are allowed to read and write temporary scratch files. SafeServer.policy demonstrates a syntax that replaces the name of a system property with the value of that property. In this way, the security policy file can be made somewhat independent of the installation location of the application.

Example 7-2. SafeServer.policy
// This file grants the SafeServer class the permissions it needs to load
// Service classes through a URLClassLoader, and grants the Service classes
// permission to read and write files in and beneath the directory specified
// by the service.tmp system property.  Note that you'll need to edit the 
// URL that specifies the location of the SafeServer class, and that for 
// Windows systems, you'll need to replace "/" with "\\"

// Grant permissions to the SafeServer class.  
// Edit the directory for your system.
grant codeBase "file:/home/david/Books/JavaExamples2/Examples" {
     // Allow the server to listen for and accept network connections 
     // from any host on any port > 1024
     permission java.net.SocketPermission "*:1024-", "listen,accept";

     // Allow the server to create a class loader to load service classes
     permission java.lang.RuntimePermission "createClassLoader";

     // Give the server permission to read the directory that contains the
     // service classes.  If we were using a network URL instead of a file URL,
     // we'd need to add a SocketPermission instead of a FilePermission
     permission java.io.FilePermission "${service.dir}/-", "read";

     // The server cannot grant permissions to the Service classes unless it
     // has those permissions itself. So we give the server these two Service
     // permissions.
     permission java.util.PropertyPermission "service.tmp", "read";
     permission java.io.FilePermission "${service.tmp}/-", "read,write";

// Grant permissions to classes loaded from the directory specified by the
// service.dir system property.  If we were using a network URL instead of a
// local file: URL, this line would have to be different.
grant codeBase "file:${service.dir}" {
     // Services can read the system property "service.tmp"      
     permission java.util.PropertyPermission "service.tmp", "read";
     // And they can read and write files in the directory specified by
     // that system property
     permission java.io.FilePermission "${service.tmp}/-", "read,write";

7.2.2 Testing SafeServer

To demonstrate that SafeServer runs its services safely, you need a demonstration service. Example 7-3 shows one such service that attempts various restricted actions and reports its results. Note that since you're going to load this class with a custom class loader, rather than from the class path, I haven't bothered to give it a package statement.

Example 7-3. SecureService.java
import je3.net.*;  // Note no package statement here.
import java.io.*;

 * This is a demonstration service.  It attempts to do things that may
 * or may not be allowed by the security policy and reports the
 * results of its attempts to the client.
public class SecureService implements Server.Service {
    public void serve(InputStream i, OutputStream o) throws IOException {
        PrintWriter out = new PrintWriter(o);

        // Try to install our own security manager.  If we can do this,
        // we can defeat any access control.
        out.println("Trying to create and install a security manager...");
        try {
            System.setSecurityManager(new SecurityManager( ));
        catch (Exception e) { out.println("Failed: " + e); }

        // Try to make the Server and the Java VM exit.
        // This is a denial of service attack, and it should not succeed!
        out.println( );
        out.println("Trying to exit...");
        try { System.exit(-1); }
        catch (Exception e) { out.println("Failed: " + e); }

        // The default system policy allows this property to be read
        out.println( );
        out.println("Attempting to find java version...");
        try { out.println(System.getProperty("java.version")); }
        catch (Exception e) { out.println("Failed: " + e); }

        // The default system policy does not allow this property to be read
        out.println( );
        out.println("Attempting to find home directory...");
        try { out.println(System.getProperty("user.home")); }
        catch (Exception e) { out.println("Failed: " + e); }

        // Our custom policy explicitly allows this property to be read
        out.println( );
        out.println("Attempting to read service.tmp property...");
        try {
            String tmpdir = System.getProperty("service.tmp");
            File dir = new File(tmpdir);
            File f = new File(dir, "testfile");

            // Check whether we've been given permission to write files to
            // the tmpdir directory
            out.println( );
            out.println("Attempting to write a file in " + tmpdir + "...");
            try {
                new FileOutputStream(f);
                out.println("Opened file for writing: " + f);
            catch (Exception e) { out.println("Failed: " + e); }

            // Check whether we've been given permission to read files from
            // the tmpdir directory
            out.println( );
            out.println("Attempting to read from " + tmpdir + "...");
            try {
                FileReader in = new FileReader(f);
                out.println("Opened file for reading: " + f);
            catch (Exception e) { out.println("Failed: " + e); }
        catch (Exception e) { out.println("Failed: " + e); }

        // Close the Service sockets
        out.close( );
        i.close( );

To test SafeServer with the SecureService class, you have to decide which directories you'll use for storing the service classes and for the scratch directory. In the material that follows, I've used /tmp/services and /tmp/scratch as the directory names.

First, compile SecureService using the -d option to tell javac where to put the resulting class file:

% javac -d /tmp/services SecureService.java

It is important that you make sure there isn't a copy of the SecureService.class file in the current directory or anywhere else that Java might find it in the local class path. If the URLClassLoader can find the class locally, it won't bother loading it through the URL you specify.

Now, to run the SafeServer class, specify the name of the SecureService class, the URL to load it from, the port to listen for connections on, and four different system properties with -D options:

% java -Djava.security.manager -Djava.security.policy=SafeServer.policy \
     -Dservice.dir=/tmp/services -Dservice.tmp=/tmp/scratch \
     je3.security.SafeServer file:/tmp/services/ \
     SecureService 4000

This is a complicated command line, but it produces the desired results. When you connect to port 4000, you get the following output from the service:

% java je3.net.GenericClient localhost 4000
Connected to localhost/
Trying to create and install a security manager...
Failed: java.security.AccessControlException: access denied
  (java.lang.RuntimePermission createSecurityManager)

Trying to exit...
Failed: java.security.AccessControlException: access denied
  (java.lang.RuntimePermission exitVM)

Attempting to find java version...

Attempting to find home directory...
Failed: java.security.AccessControlException: access denied
  (java.util.PropertyPermission user.home read)

Attempting to read service.tmp property...

Attempting to write a file in /tmp/scratch...
Opened file for writing: /tmp/scratch/testfile

Attempting to read from /tmp/scratch...
Opened file for reading: /tmp/scratch/testfile
Connection closed by server.
    [ Team LiB ] Previous Section Next Section