Before Java applets arrived on the scene, the Web was primarily composed of static content and was relatively safe. Of course, CGI scripts and server-side includes opened up security concerns on the server side of the equation-but with applets, the security of the client is in question for the first time. Web developers are now able to create active content that could be executed in the client's address space. Needless to say, it is not acceptable to give wholesale access to the client's system resources. As illustrated in Figure 20.1, not only can applets originate from unknown or untrusted hosts on the Internet, the applet's class files must travel across an untrusted network before being executed on the user's system. A bulletproof security scheme is needed to allow users of the Web to run any applet without having to worry about their local files being deleted or their system being raided for sensitive information. This chapter discusses the security measures of the Java language and runtime environment.
Figure 20.1 : The origins and paths of Java applets.
All Web browsers offer some level of control when it comes to executing applets. Of course, browsers that cannot execute applets offer the most security, but the others still allow users to customize the level of security to suit their needs. Most of the time this means simply enabling or disabling the execution of applets. However, Sun's HotJava browser offers a finer level of control where access to local files and network ports is fully configurable. Figure 20.2 displays the security configuration dialog for the Microsoft Internet Explorer 3.0.
Figure 20.2 : The security dialog for Internet Explorer.
The challenge is to allow an applet to perform its intended tasks,
and at the same time isolate the applet from vital resources and
the other processes on the host system. This concept is often
referred to as the sandbox approach. In general, applets
must operate under the constraints listed in Table 20.1.
| Resource | Constraint |
| File system | Unless otherwise configured through the browser, applets are not allowed to read, write, or manipulate files on the host machine in any way. |
| Network connections | Connections can be made only back to the server from which the applet came (as specified by the CODEBASE parameter of the APPLET HTML tag) or the server hosting the Web page referencing the applet. |
| Environment variables | Unless otherwise configured through the browser, access to environment variables via System.getProperty() is prohibited. |
| System calls | Starting processes, executing programs, or loading libraries on the local machine is prohibited. Furthermore, applets cannot exit or terminate the browser through calls to System.exit(). |
| Thread control | Unauthorized manipulation of threads within the Java Virtual Machine is prohibited. |
| Pop-up windows | Although pop-up windows can be created with applets, they are tagged in some manner to indicate that they originated from an applet. |
To enforce the constraints listed in Table 20.1, the development and runtime environments must work together. On the development side, the language itself has many attributes that make it "safe" and difficult to corrupt the runtime environment. Because the browser actually runs applets, it has the most security measures. Not only are the bytecodes of each class file verified, the loading of each class is scrutinized as well as access attempts to critical resources. The following sections discuss each of these areas in detail.
The creators of Java knew that if the language was going to succeed as a tool for developing active content for the Internet, the language itself would have to be safe. In fact, many of the attributes of Java that make it safe also protect programmers from the pitfalls of other languages. Perhaps one of the most obvious of these is the absence of pointers in Java. C and C++ programmers have had a love/hate relationship with pointers for many years. On one hand, pointers add flexibility and efficiency to a language. However, it is the flexibility of pointers that can add hours of debugging to a project when they are left uninitialized, pointing at deleted objects, or used to reference objects of incompatible types. As far as security is concerned, the elimination of pointers immediately protects Java programs from using them to imitate objects, violate encapsulation, and access protected areas of memory. In addition, all array access is validated to prevent out-of-bounds conditions that can lead to unauthorized access to memory.
As an alternative to pointers, Java uses strongly typed object references that are policed by the compiler and runtime environment. The compiler will flag casts between incompatible types, and all seemingly legitimate casts undergo runtime checks for casting violations. Illegal runtime casts generate ClassCastExceptions.
Memory management is also a vital ingredient to the security of Java. The garbage collector ensures that objects are disposed of only when they are no longer referenced. This prevents a program from accidentally exhausting the resources of the host system, thereby crashing or severely affecting the stability of the system. Of course, this does not prevent an evil applet from purposefully over-allocating resources to achieve the same end.
In addition, the Java compiler ensures that all class method and variable controls are enforced. That is, protected methods and variables are accessed internally only by a class or subclass and private methods and variables are accessed internally by the class. The final modifier also contributes to the security of programs by preventing critical classes from being subclassed to override or circumvent access controls.
So the language specification and compiler work together to produce secure bytecodes in the form of .class files that can then be interpreted by the Java runtime environment. Unfortunately, a lot can happen to the bytecodes from the time they are constructed to the time they are interpreted. They could be modified on the file system where they reside or by a node on the Internet as the bytecode passes by. Of course, with the proliferation of Java compilers available today, the compiler itself could also produce bytecodes with security flaws. What is needed is another measure of security that ensures the bytecodes meet the same constraints imposed by the development environment.
The purpose of the code verifier is to validate the bytecodes of any class files loaded from outside the trusted domain of the runtime environment. Typically this involves any classes loaded from a network connection. Classes on the host's local disk are considered trusted and thus are not subjected to the verifier. Figure 20.3 shows the code verifier's position in the security chain.
Figure 20.3 : Java's security scheme.
The verifier makes four passes over each class file. The first pass involves validating the format of the class file. Each class file includes the following components:
The first pass will calculate the magic number for the class file and compare it against the magic number embedded in the file. It will also do some basic validation to verify that all recognized attributes have the correct length and the constant pool contains the proper information.
The second pass looks a little closer at the class file and ensures that final classes are not subclassed; final methods are not overridden, every class has a superclass; and the names, classes, and type signatures referenced in the constant pool are legal.
The third and most complex pass actually verifies the bytecodes of each method. The stack, registers, method arguments, and opcodes are all validated in this step.
Finally, the fourth pass completes the verification process by completing tests that were deferred by the third pass as well as performing some opcode optimization.
If the verifier approves a class file to be executed, the interpreter can assume that the bytecodes possess the following qualities:
Because the verifier ensures several runtime boundaries are maintained, the interpreter is free to run verified code much faster. Runtime checks that would ordinarily have to be done with the execution of each instruction are now eliminated. However, this also illustrates how vital the verifier is to the runtime environment. Just as the security of bytecodes is the responsibility of the compiler, the security and stability of the runtime environment is only as good as the verifier of the browser used to run applets. Indeed, browser vendors have had to enhance the effectiveness of their Java runtime environments to close potential security holes opened by the verifier.
Now that the bytecodes have been authorized to be safely interpreted, the ClassLoader takes over to ensure that class boundaries and namespaces are maintained. A hierarchical chain of namespaces begins with the classes loaded in the local namespace and ends with classes loaded from network connections. The local namespace is composed of the core Java classes as referenced by the CLASSPATH environment variable. Because these classes are trusted and implement vital objects such as System, they cannot be replaced by classes from any of the other namespaces. The class loader also ensures that namespaces are maintained between classes loaded from different applets from separate network sources.
This is where the package names and access modifiers come into play. The class loader keeps the classes of each package isolated and prioritized so applets cannot create their own versions of classes by the same name in different packages. In addition, access to classes that are not indicated as public by objects outside of the package is prohibited.
Now that all of the classes necessary to run an applet have been
verified by the verifier and separated into executable compartments
by the class loader, the security manager implements the security
policy of the runtime environment. This is where perfectly legitimate
requests under normal circumstances are denied by the browser.
Resources such as the file system and network are protected by
the security manager. Table 20.2 lists some of the methods of
the abstract SecurityManager class.
| Method | Description |
| checkAccept(String, int) | Called prior to accepting a socket connection from the specified host on the specified port. |
| checkAccess(Thread) | Called prior to performing any thread manipulation requests on the specified thread. |
| checkAccess(ThreadGroup) | Called prior to performing any thread group manipulation requests on the specified thread group. |
| checkConnect(String, int) | Called prior to opening a socket connection to the specified host and port. |
| checkConnect(String, int, Object) | Called prior to opening a socket to the specified host and port for the given security context. |
| CheckCreateClassLoader() | Called to check whether a new ClassLoader object can be set. |
| checkDelete(String) | Called to determine whether the specified file is permitted to be deleted. |
| checkExec(String) | Called to determine whether a subprocess can be created for the specified command. |
| checkExit(int) | Called to determine whether the current process can halt the Java Virtual Machine with the specified exit code. |
| checkLink(String) | Called to determine whether the specified library is permitted to be dynamically loaded. |
| checkListen(int) | Called to determine whether the current process is permitted to listen for connections on the specified port. |
| CheckPackageAccess (String) | This method works with the class loader to determine whether the current process is permitted to access the specified package. |
| CheckPackageDefinition (String) |
Determines whether the current process is permitted to define classes within the specified package. |
| CheckPropertiesAccess() | Called to determine whether system properties can be accessed. |
| CheckPropertyAccess (String) | Called to determine whether the current process is permitted to access the specified environment variable. |
| checkRead (FileDescriptor) | Called to determine whether the current process is permitted to read the file indicated by the FileDescriptor. |
| checkRead(String) | Called to determine whether the current process is permitted to read the file by the specified name. |
| checkRead(String, Object) | Called to determine whether the specified file can be read within the given security context. |
| checkSetFactory() | Called to determine whether the current process is permitted to set the socket or URL stream handler factory. |
| boolean checkTopLevelWindow (Object) | Called to determine whether a top-level window can be displayed without display restrictions (like a warning indicator). |
| checkWrite (FileDescriptor) | Called to determine whether the current process can write to the file indicated by the FileDescriptor. |
| checkWrite(String) | Called to determine whether the current process can write to the file by the specified name. |
| boolean getInCheck() | Indicates whether a security check is currently in progress. |
| Object getSecurityContext() | Returns an object that contains information about the current execution environment to be used in subsequent security checks. |
The implementation of the SecurityManager class is really quite simple. In fact, most of the methods in Table 20.2 throw only SecurityExceptions. The idea is that if you subclass SecurityManager and install your own version using System.setSecurityManager(), access to all of the resources under the security manager's control are immediately denied. In fact, this is exactly what the Web browser does (with some overridden behavior, of course). To prevent applets from installing their own more lenient security managers, the runtime system allows the security manager to be set only once.
The security manager is prewired into many of Java's classes in such a way that it cannot be bypassed. For example, before a Socket object can be successfully created, the Socket class checks to see whether a security manager has been set and if so whether a socket to the specified host and port is allowed.
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkConnect(address.getHostAddress(), port);
}
In addition, calls to the security manager are embedded in classes or methods that are declared final so they cannot be overridden by subclasses.
Up to this point, we have focused on the security constraints used to control the execution of applets. However, Java applets and applications are handled quite differently in regard to security. Because applications are explicitly executed and are from a (hopefully) trusted source, the security constraints imposed on applets are relaxed for applications. However, applications can still benefit from the built-in security features of the Java language itself as already mentioned. Furthermore, because portions of the security scheme used to monitor applets are controlled by Java classes, applications can implement their own brand of security, if necessary. The ClassLoader and SecurityManager classes already covered can be subclassed and installed by applications just as they are by the browser's Java Virtual Machine. A customized ClassLoader could provide a class cache for a networked application to cut down on the number of calls across a slow network connection for classes it has already retrieved. Likewise, a subclass of SecurityManager could be used to control what directories or files they are allowed to work with.
The security barriers imposed by Java are really quite formidable. However, Java security is not infallible. In fact, there have been some well-documented holes and back doors to the system. But Sun has been quick to answer such claims and has refined the language, compiler, bytecode verifier, class loader, and security manager over the last several releases. Likewise, Sun, Netscape, and Microsoft have also closed security holes in their browsers related to Java applets over the last several months. In fact, most of the security concerns that have been uncovered have dealt with the implementation of the runtime environment in browsers and not the language itself. By releasing Java and the source code for its tools to the public domain, Sun has not only allowed the world to peek into every crack and crevice of Java but has also been able to rapidly solidify the language.
The future of security and active content on the Internet will continue to evolve. With the increase in use of digital signatures to authenticate the content of data, the security and confidence of dealing with information will continue to improve. Java can supplement its security model by also incorporating digital signatures. Just as the structural integrity of class files are inspected by the code verifier, it will not be long before a digital signature may also be embedded within each class file. The end result is that Web developers and users will be offered yet another level of control to safely build, retrieve, and run active content on their systems.