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.
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.
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:
The programmer makes a request of the Java API to perform an operation.
The Java API asks the security manager if such an operation is allowable.
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.
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.
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.