// This example is from _Java Examples in a Nutshell_. (http://www.oreilly.com) // Copyright (c) 1997 by David Flanagan // This example is provided WITHOUT ANY WARRANTY either expressed or implied. // You may study, use, modify, and distribute it for non-commercial purposes. // For any commercial use, see http://www.davidflanagan.com/javaexamples import java.net.*; import java.io.*; import Server.*; /** * This class is a program that uses the Server class defined in Chapter 9. * 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 the Security manager System.setSecurityManager(new ServiceSecurityManager()); // Create the ClassLoader that we'll use to load Service classes. // The classes should be stored in (or beneath) the directory specified // as the first command-line argument LocalClassLoader loader = new LocalClassLoader(args[0]); // Create a Server object that does no logging and // has a limit of five concurrent connections at once. Server server = new Server(null, 5); // 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) { Class serviceClass = loader.loadClass(args[i++]); // dynamic load Service service = (Service)serviceClass.newInstance(); // instantiate int port = Integer.parseInt(args[i++]); // get port server.addService(service, port); // run service } } catch (Exception e) { // Display a message if anything goes wrong System.err.println(e); System.err.println("Usage: java SafeServer " + " \n" + "\t\t[ ... ]"); System.exit(1); } } /** * This is a fairly uptight security manager subclass. Classes loaded by * a ClassLoader are highly restricted in what they are allowed to do. * This is okay, because our Service classes have a very narrowly defined * task: to read from one stream and send output to another. * * A SecurityManager consists of various methods that the system calls to * check whether certain sensitive operations should be allowed. These * methods can throw a SecurityException to prevent the operation from * happening. With this SecurityManager, we want to prevent untrusted * code that was loaded by a class loader from performing those sensitive * operations. So we use inherited SecurityManager methods to check * whether the call is being made by an untrusted class. If it is, we * throw an exception. Otherwise, we simply return, allowing the * operation to proceed normally. **/ public static class ServiceSecurityManager extends SecurityManager { /** * This is the basic method that tests whether there is a class loaded * by a ClassLoader anywhere on the stack. If so, it means that that * untrusted code is trying to perform some kind of sensitive operation. * We prevent it from performing that operation by throwing an exception. * trusted() is called by most of the check...() methods below. **/ protected void trusted() { if (inClassLoader()) throw new SecurityException(); } /** * This is a variant on the trusted() method above. There are a couple * of methods that loaded code should not be able to call directly, but * which system methods invoked by the loaded code still need to be * able to call. So for these, we only throw an exception if a * loaded class is the third thing on the call stack. I.e. right above * this method and the check...() method that invoked it **/ protected void trustedOrIndirect() { if (classLoaderDepth() == 3) throw new SecurityException(); } /** * Here's another variant. It denies access if a loaded class attempts * the operation directly or through one level of indirection. It is used * to prevent loaded code from calling Runtime.load(), or * System.loadLibrary() (which calls Runtime.load()). **/ protected void trustedOrIndirect2() { int depth = classLoaderDepth(); if ((depth == 3) || (depth == 4)) throw new SecurityException(); } /** * These are all the specific checks that a security manager can * perform. They all just call one of the methods above and throw a * SecurityException if the operation is not allowed. This * SecurityManager subclass is perhaps a little too restrictive. For * example, it doesn't allow loaded code to read *any* system properties, * even though some of them are quite harmless. **/ public void checkCreateClassLoader() { trustedOrIndirect(); } public void checkAccess(Thread g) { trusted(); } public void checkAccess(ThreadGroup g) { trusted(); } public void checkExit(int status) { trusted(); } public void checkExec(String cmd) { trusted(); } public void checkLink(String lib) { trustedOrIndirect2(); } public void checkRead(FileDescriptor fd) { trusted(); } public void checkRead(String file) { trusted(); } public void checkRead(String file, Object context) { trusted(); } public void checkWrite(FileDescriptor fd) { trusted(); } public void checkWrite(String file) { trusted(); } public void checkDelete(String file) { trusted(); } public void checkConnect(String host, int port) { trusted(); } public void checkConnect(String host,int port,Object context) {trusted();} public void checkListen(int port) { trusted(); } public void checkAccept(String host, int port) { trusted(); } public void checkMulticast(InetAddress maddr) { trusted(); } public void checkMulticast(InetAddress maddr, byte ttl) { trusted(); } public void checkPropertiesAccess() { trustedOrIndirect(); } public void checkPropertyAccess(String key) { trustedOrIndirect(); } public void checkPrintJobAccess() { trusted(); } public void checkSystemClipboardAccess() { trusted(); } public void checkAwtEventQueueAccess() { trusted(); } public void checkSetFactory() { trusted(); } public void checkMemberAccess(Class clazz, int which) { trusted(); } public void checkSecurityAccess(String provider) { trusted(); } /** Loaded code can only load classes from java.* packages */ public void checkPackageAccess(String pkg) { if (inClassLoader() && !pkg.startsWith("java.")) throw new SecurityException(); } /** Loaded code can't define classes in java.* or sun.* packages */ public void checkPackageDefinition(String pkg) { if (inClassLoader() && (pkg.startsWith("java.")||pkg.startsWith("sun."))) throw new SecurityException(); } /** * This is the one SecurityManager method that is different from the * others. It indicates whether a top-level window should display an * "untrusted" warning. The window is always allowed to be created, so * this method is not normally meant to throw an exception. It should * return true if the window does not need to display the warning, and * false if it does. In this example, however, our text-based Service * classes should never need to create windows, so we will actually * throw an exception to prevent any windows from being opened. **/ public boolean checkTopLevelWindow(Object window) { trusted(); return true; } } /** * In order to impose tight security restrictions on untrusted classes but * not on trusted system classes, we have to be able to distinguish between * those types of classes. This is done by keeping track of how the classes * are loaded into the system. By definition, any class that the interpreter * loads directly from the CLASSPATH is trusted. This means that we can't * load untrusted code in that way--we can't load it with Class.forName(). * Instead, we create a ClassLoader subclass to load the untrusted code. * This one loads classes from a specified directory (which should not * be part of the CLASSPATH). **/ public static class LocalClassLoader extends ClassLoader { /** This is the directory from which the classes will be loaded */ String directory; /** The constructor. Just initialize the directory */ public LocalClassLoader(String dir) { directory = dir; } /** A convenience method that calls the 2-argument form of this method */ public Class loadClass(String name) throws ClassNotFoundException { return loadClass(name, true); } /** * This is one abstract method of ClassLoader that all subclasses must * define. Its job is to load an array of bytes from somewhere and to * pass them to defineClass(). If the resolve argument is true, it must * also call resolveClass(), which will do things like verify the presence * of the superclass. Because of this second step, this method may be * called to load superclasses that are system classes, and it must take * this into account. **/ public Class loadClass(String classname, boolean resolve) throws ClassNotFoundException { try { // Our ClassLoader superclass has a built-in cache of classes it has // already loaded. So, first check the cache. Class c = findLoadedClass(classname); // After this method loads a class, it will be called again to // load the superclasses. Since these may be system classes, we've // got to be able to load those too. So try to load the class as // a system class (i.e. from the CLASSPATH) and ignore any errors if (c == null) { try { c = findSystemClass(classname); } catch (Exception e) {} } // If the class wasn't found by either of the above attempts, then // try to load it from a file in (or beneath) the directory // specified when this ClassLoader object was created. Form the // filename by replacing all dots in the class name with // (platform-independent) file separators and by adding the // ".class" extension. if (c == null) { // Figure out the filename String filename = classname.replace('.',File.separatorChar)+".class"; // Create a File object. Interpret the filename relative to the // directory specified for this ClassLoader. File f = new File(directory, filename); // Get the length of the class file, allocate an array of bytes for // it, and read it in all at once. int length = (int) f.length(); byte[] classbytes = new byte[length]; DataInputStream in = new DataInputStream(new FileInputStream(f)); in.readFully(classbytes); in.close(); // Now call an inherited method to convert those bytes into a Class c = defineClass(classname, classbytes, 0, length); } // If the resolve argument is true, call the inherited resolveClass // method. if (resolve) resolveClass(c); // And we're done. Return the Class object we've loaded. return c; } // If anything goes wrong, throw a ClassNotFoundException error catch (Exception e) { throw new ClassNotFoundException(e.toString()); } } } /** * This is a demonstration service. It attempts to do things that the * ServiceSecurityManager doesn't allow, and sends the results of * its attempts to the client. **/ public static class ProhibitedService implements Service { public void serve(InputStream i, OutputStream o) throws IOException { PrintWriter out = new PrintWriter(new OutputStreamWriter(o)); out.print("Attempting to read a file..."); try { new FileInputStream("testfile"); } catch (Exception e) { out.println("Failed: " + e); } out.print("Attempting to write a file..."); try { new FileOutputStream("testfile"); } catch (Exception e) { out.println("Failed: " + e); } out.print("Attempting to read system property..."); try { System.getProperty("java.version"); } catch (Exception e) { out.println("Failed: " + e); } out.print("Attempting to load a library..."); try { Runtime.getRuntime().load("testlib"); } catch (Exception e) { out.println("Failed: " + e); } out.close(); i.close(); } } }