Chapter 4. The Security Manager Class

When most people think of Java security, they think of the protections afforded to a Java program—and, more particularly, only by default to a Java applet—by Java’s security manager. As we’ve seen, there are other important facets of Java’s security story, but the role played by the security manager is of paramount importance in the degree to which your machine will be safe from malicious Java programs.

On one level, the Java security manager is simple to understand, and it’s often summarized by saying that it prevents Java applets from accessing your local disk or local network. The real story is more complicated than that, however, with the result that Java’s security manager is often misunderstood. In this chapter, we’ll look into how the security manager actually works, what it can and can’t do, and when it does—and doesn’t—protect you. In this chapter, we’re only going to look at the security manager in terms of its capabilities, with an emphasis on how those capabilities are used by popular browsers; we’ll look into writing our own security manager in the next few chapters.

Overview of the Security Manager

On a simple level, the security manager is responsible for determining most of the parameters of the Java sandbox—that is, it is ultimately up to the security manager to determine whether many particular operations should be permitted or rejected. If a Java program attempts to open a file, the security manager decides whether or not that operation should be permitted. If a Java program wants to connect to a particular machine on the network, it must first ask permission of the security manager. If a Java program wants to alter the state of certain threads, the security manager will intervene if such an operation is considered dangerous.

The security manager is of particular concern to authors and users of Java applets. In general, Java applications do not have security managers—unless the author of the application has provided one. Historically, that’s been a somewhat unusual occurrence, even though there are many times when you might want a security manager in your Java application; this stems from the fact that before Java 1.2,[11] writing a security manager was more difficult than it is now. Beginning in 1.2, there is a default, user-configurable security manager that is suitable for most applications, one which can even be installed via a command-line argument when starting an application. This brings the benefits of a security manager to an application without requiring any programming. And we’ll show how to write your own (non-default) security manager for the JavaRunner program in Chapter 6.

But this point cannot be overemphasized: Java applications (at least by default) have no security manager, while Java applets (again, by default) have a very strict security manager. This leads to a common misconception that exists in the arena of Java security: it’s common to think that because Java is said to be secure, it is always secure, and that running Java applications that have been installed locally is just as secure as running Java applets inside a Java-enabled browser. Nothing is further from the truth.

To illustrate this point, consider the following malicious code:

public class MaliciousApplet extends Applet {
	public void init() {
		try {
			Runtime.getRuntime().exec("/bin/rm -rf .");
		} catch (Exception e) {}
	}
	public static void main(String args[]) {
		MaliciousApplet a = new MaliciousApplet();
		a.init();
	}
}

If you compile this code, place it on your web server, and load it as an applet, you’ll get an error reflecting a security violation. However, if you compile this code, place it in a directory, and run it as an application, you’ll end up deleting all the files in your current directory.[12] As a user, then, it’s crucial that you understand which security manager is in place when you run a Java program so that you understand just what types of operations you are protected against.

Security Managers and the Java API

The security manager can be considered a partnership between the Java API and the implementor of a specific Java application or of a specific Java-enabled browser. There is a class in the Java API called SecurityManager (java.lang.SecurityManager) which is the linchpin of this partnership—it provides the interface that the rest of the Java API uses to check whether particular operations are to be permitted. The essential algorithm the Java API uses to perform a potentially dangerous operation is always the same:

  1. The programmer makes a request of the Java API to perform an operation.

  2. The Java API asks the security manager if such an operation is allowable.

  3. If the security manager does not want to permit the operation, it throws an exception back to the Java API, which in turn throws it back to the user.

  4. Otherwise, the Java API completes the operation and returns normally.

Let’s trace this idea with the example that we first saw in Chapter 1:

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);
		}
	}
}

The FileReader object will in turn create a FileInputStream object, and constructing the input stream is the first step of the algorithm. When the input stream is constructed, the Java API performs code similar to this:

public FileInputStream(String name) throws FileNotFoundException {
	SecurityManager security = System.getSecurityManager();
	if (security != null) {
		security.checkRead(name);
	}
	try {
		open(name); // open() is a private method of this class
	} catch (IOException e) {
		throw new FileNotFoundException(name);
	}
}

This is step two of our algorithm and is the essence of the idea behind the security manager: when the Java API wants to perform an operation, it first checks with the security manager and then calls a private method (the open() method in this case) that actually performs the operation.

Meanwhile, the security manager code is responsible for deciding whether or not the file in question should be allowed to be read and, if not, for throwing a security exception:

public class SecurityManagerImpl extends SecurityManager {
    public void checkRead (String s) {
        if (theFileIsNotAllowedToBeRead)
            throw new SecurityException ("checkread");
    }
}

The SecurityException class is a subclass of the RuntimeException class. Remember that runtime exceptions are somewhat different than other exceptions in Java in that they do not have to be caught in the code—which is why the checkRead() method does not have to declare that it throws that exception, and the FileInputStream constructor does not have to catch it. So if the security exception is thrown by the checkRead() method, the FileInputStream constructor will return before it calls the open() method—which is simply to say that the input file will never be opened, because the security manager prevented that code from being executed.

Typically, the security exception propagates up through all the methods in the thread that made the call; eventually, the top-most method receives the exception, which causes that thread to exit. When the thread exits in this way, it prints out the exception and the stack trace of methods that led it to receive the exception. This leads to the messages that you’ve probably seen in your Java console:

sun.applet.AppletSecurityException: checkread
		at sun.applet.AppletSecurity.checkRead(AppletSecurity.java:427) 
		at java.io.FileOutputStream.<init>(FileOutputStream.java)
		at Cat.init(Cat.java:7)
		at sun.applet.AppletPanel.run(AppletPanel.java:273)
		at java.lang.Thread.run(Thread.java)

If the security exception is not thrown—that is, if the security manager decides that the particular operation should be allowed—then the method in the security manager simply returns, and everything proceeds as expected.

Several methods in the SecurityManager class are similar to the checkRead() method. It is up to the Java API to call those methods at the appropriate time. You may want to call those methods from your own Java code (using the technique shown above), but that’s never required. Since the Java API provides the interface to the virtual operating system for the Java program, it’s possible to isolate all the necessary security checks within the Java API itself.

One exception to this guideline occurs when you extend the virtual operating system of the Java API, and it is important to ensure that your extensions are well-integrated into Java’s security scheme. Certain parts of the Java API—the Toolkit class, the Provider class, the Socket class, and others—are written in such a way that they allow you to provide your own implementation of those classes. If you’re providing your own implementation of any of these classes, you have to make sure that it calls the security manager at appropriate times.

It’s important to note that there is (by design) no attempt in the Java API to keep any sort of state. Whenever the Java API needs to perform an operation, it checks with the security manager to see if the operation is to be allowed—even if that same operation has been permitted by the security manager before. This is because the context of the operation is often significant—the security manager might allow a FileOutputStream object to be opened in some cases (e.g., by certain classes) while it might deny it in other cases. The Java API cannot keep track of this contextual information, so it asks the security manager for permission to perform every operation.



[11] 1.2 is now Java 2.

[12] The example will only delete the files in your current directory if you run it on a Unix system, but we could have included similar code for any other operating system.

Get Java Security now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.