This month's article continues the discussion of Java's security model that began in August's "Under the Hood." In that article, I sketched overview of the security mechanisms built into the Java virtual machine (JVM). I also looked closely at one aspect of those security mechanisms: the JVM's built-in safety features. In September's column I examined the class loader architecture, and in the October column, the class verifier. In this installment of the security series, I describe the security manager -- the fourth and final piece of the JVM's core security architecture -- and I finish up with a brief discussion of the ways in which Java's security strategy extends beyond the JVM's architecture.
The security manager and the Java API
As described in last month's "Under the Hood," you can prevent code loaded by different class loaders from interfering with one another inside the JVM by using a class-file verifier. But to protect assets external to the Java virtual machine, you must use a security manager. The security manager defines the outer boundaries of the sandbox. (For a refresher on the Java sandbox, see the first section of my August "Under the Hood" column.)
A security manager is any class that descends from class
java.lang.SecurityManager. Because they are written in Java, security managers are customizable. A security manager allows you to establish a custom security policy for an application.
The Java API enforces the custom security policy by asking the security manager for permission to take any action before it does something that potentially is unsafe. For each potentially unsafe action, there is a method in the security manager that defines whether or not that action is allowed by the sandbox. Each method's name starts with "check," so, for example,
checkRead() defines whether or not a thread is allowed to read to a specified file, and
checkWrite() defines whether or not a thread is allowed to write to a specified file. The implementation of these methods is what defines the custom security policy of the application.
Most of the activities that are regulated by a "check" method are listed below. The classes of the Java API check with the security manager before they:
- Accept a socket connection from a specified host and port number
- Modify a thread (change its priority, stop it, and so on)
- Open a socket connection to a specified host and port number
- Create a new class loader
- Delete a specified file
- Create a new process
- Cause the application to exit
- Load a dynamic library that contains native methods
- Wait for a connection on a specified local port number
- Load a class from a specified package (used by class loaders)
- Add a new class to a specified package (used by class loaders)
- Access or modify system properties
- Access a specified system property
- Read from a specified file
- Write to a specified file
Because the Java API always checks with the security manager before it performs any of the activities listed above, the Java API will not perform any action forbidden under the security policy established by the security manager.
Areas unprotected by the security manager
Two actions not present in the above list that could potentially be unsafe are allocation of memory and invocation of threads. Currently, a hostile applet can crash a user's browser by:
- Allocating memory until it runs out
- Firing off threads until everything slows to a crawl
These kinds of attacks are called denial of service attacks, because they deny users the ability to use their own computers. The security manager does not allow you to enforce any kind of limit on allocated memory or thread creation. (There are no
checkCreateThread() methods in the security manager class.) The following are other kinds of hostile applets that currently are possible:
- Applets that send unauthorized e-mail from the user's computer
- Applets that make annoying noises even after you leave the Web page
- Applets that display offensive images or animations
So, a security manager isn't enough to prevent every possible action that could offend or inconvenience a user. Other than the attacks listed here, however, the security manager attempts to provide a check method that allows you to control access to any potentially unsafe action.
Installing a security manager
When a Java application starts, it has no security manager. At its option, the application can install one. If it does not install a security manager, no restrictions are placed on any activities requested of the Java API; the Java API will do whatever it is asked. (This is why Java applications, by default, do not have any security restrictions such as those that limit the activities of untrusted applets.) If the application does install a security manager, then that security manager will be in charge for the entire lifetime of that application. It can't be replaced, extended, or changed. From that point on, the Java API will fulfill only those requests that are sanctioned by the security manager.
In general, a "check" method of the security manager throws a security exception if the checked-upon activity is forbidden, and simply returns if the activity is permitted. Therefore, the procedure a Java API method generally follows when it is about to perform a potentially unsafe activity involves two steps. First, the Java API code checks whether a security manager has been installed. If not, it doesn't move to step two but goes ahead with the potentially unsafe action. If a security manager has been installed, the API code enacts step two, which is to call the appropriate "check" method in the security manager. If the action is forbidden, the "check" method will throw a security exception, which will cause the Java API method to abort immediately. The potentially unsafe action will never be taken. If, on the other hand, the action is permitted, the "check" method will simply return. In this case, the Java API method carries on and performs the potentially unsafe action.
Although you can install only one security manager, you can write the security manager so that it establishes multiple security policies. In addition to the "check" methods, the security manager also has methods that allow you to determine if a request is being made either directly or indirectly from a class loaded by a class loader object, and if so, by which class loader object. This enables you to implement a security policy that varies depending on which class loader loaded the classes making the request. You can also vary the security policy based on information about the class files loaded by the class loader, such as whether or not the class files were downloaded across a network or imported from the local disk. So even though an application can have only one security manager, that security manager can establish a flexible security policy that varies based on the trustworthiness of the code requesting the potentially unsafe action.
The support for authentication introduced in Java 1.1 in the
java.security package expands your ability to establish multiple security policies by enabling you to implement a sandbox that varies depending on who actually created the code. Authentication allows you to verify that a set of class files was blessed as trustworthy by some vendor, and that the class files were not altered en route to your virtual machine. Thus, to the extent you trust the vendor, you can ease the restrictions placed on the code by the sandbox. You can establish different security policies for code that comes from different vendors.
For links to more information about authentication and
java.security, see the Resources at the bottom of this article.
Security beyond the architecture
To be effective, a computer or network security strategy must be comprehensive. It cannot consist exclusively of a sandbox for running downloaded Java code. For instance, it may not matter much that the Java applets you download from the Internet and run on your computer can't read the word processing file of your top-secret business plan if you:
- Routinely download untrusted native executables from the Internet and run them
- Throw away extra printed copies of your business plan without shredding them
- Leave your doors unlocked when you're gone
- Hire someone to help you who is actually a spy for your arch-rival
In the context of a comprehensive security strategy, however, Java's security model can play a useful role.
Security is a tradeoff between cost and risk: The lower the risk of a security breach, the higher the cost of security. The costs associated with any computer or network security strategy must be weighed against the costs that would be associated with the theft or destruction of the information or computing resources being protected. The nature of a computer or network security strategy should be shaped by the value of the assets being protected.
The nice thing about Java's security model is that once you set it up, it does most of the work for you. You don't have to worry about whether a particular program is trusted or not -- the Java runtime will determine that for you. If the program is untrusted, the Java runtime will protect your assets by encasing the untrusted code in a sandbox.
Java's overall security strategy
Just as users of Java software must have a comprehensive security policy appropriate to their requirements, the security strategy of Java technology itself does not rely exclusively on the architectural security mechanisms described in this section. For example, one aspect of Java's security strategy is that anyone can sign a license agreement and get a copy of the source code of Sun's Java Platform implementation. Instead of keeping the internal implementation of Java's security architecture a secret "black box," it is open to anyone who wishes to look at it. This encourages security experts seeking a good technical challenge to seek out security holes in the implementation. When security holes are discovered, they can be patched. Thus, the openness of Java's internal implementation is part of Java's overall security strategy.
Besides openness, there are several other aspects to Java's overall security strategy that don't directly involve its architecture. You can find links to more information about these in the Resources section at the bottom of this article.
The security manager contributes to the JVM's security model by establishing a custom security policy for Java applications. For the security policy to be "bullet proof," both the Java API and the security manager itself must be implemented properly. A bug in either of these can result in a security hole that malicious programmers could exploit.
The customizable nature of the security manager is one of the strengths of Java's security architecture. The security manager's "check" methods are just Java code, so you are free to decide the exact circumstances in which your application will permit potentially unsafe actions. If you can express an algorithm in Java code as a "check" method of the security manager, that algorithm can be part of your application's custom security policy.