The Execution Environment
We have said that the JVM operates on the stream of bytecode as an interpreter. This means that it processes bytecode while the program is running and converts it to real machine code that it executes on the fly. You can think of a computer program as being like a railroad track, with the train representing the execution point at any given time. In the case of an interpreted program it is as if this train has a machine mounted on it, which builds the track immediately in front of the train and tears it up behind. It’s no way to run a railroad.
Fortunately, in the case of Java, the JVM is not interpreting high-level language instructions, but bytecode. This is really machine code, written for the JVM instruction set, so the interpreter has much less analysis to do, resulting in execution times that are very fast. The JVM often uses just-in-time (JIT) compiler techniques to allow programs to execute faster, for example, by translating bytecode into optimized local code once and subsequently; running it directly. Advances in JIT technology are making Java run faster all the time. IBM is one of many organizations exploring the technology.
Before the JVM can start this interpretation process, it has to do a number of things to set up the environment in which the program will run. This is the point at which the built-in security of Java is implemented. There are three parts to the process:
1. The first component of code checking is the class loader. This separates the classes it loads to avoid attack. Java built-in classes, specified in the boot class path (also known as system class path or JVM class path), are separated from extension classes, specified in the extension class path, and from other application classes, specified in the user or application class path variable. An extension is a group of Java packages that implement an API extending the Java platform, such as JavaServlet, Java3D, JavaManagement, etc. The search order is Java built-in classes first, extension classes and then application classes last. So, if, by accident or design, any application code contains a class of the same name as a built-in or extension class, the built-in or extension class will not be overwritten by the application code.
2. The second component is the class file verifier. This runs when the code is loaded, and confirms that the bytecode program is legal Java code and obeys the rules of the language. It is a multipass process which begins by making sure that the syntax is valid, checks for stack overflow or underflow, and runs a theorem prover that looks to see that access and type restrictions are observed.
3. The third component is the security manager, which checks sensitive accesses at run time. This is the component that enforces the security policy defined for the system and will not allow Java code illicit access to the file system, or to the network, or to the run-time operating system.
The Execution process can be summarized as shown in the following figure:
Figure: Execution Process
The Class Loader
Before the JVM can run a Java program, it needs to locate and load the classes which comprise that program into memory.
In a traditional execution environment, this service is provided by the operating system which loads code from the file system in a platform-specific way. The operating system has access to all of the low level I/O functions and has a set of locations on the file system that it searches for programs or shared code libraries. Depending on the operating system, this can be a list of directories to look in using environment variables, such as Path and CLASSPATH.
In the JRE things can get a little more complicated by the fact that not all class files are loaded from the same type of location and may not be under the local operating system’s control to ensure integrity. The class loading mechanism plays a critical role in Java security since the class loader is responsible for locating and fetching the class files, consulting the security policy, and defining the class object with the appropriate permissions. So how do classes get loaded? We answer this question taking as an example the PointlessButton applet, whose code is shown in Figure on previous post . When the browser finds the <APPLET> tag in the HTML , it starts the JVM which, in turn, invokes the applet class loader. This is, itself, a Java class which contains the code for fetching the bytecode of the applet and presenting it to the JVM in an executable form. The bytecode includes a list of referenced classes and the JVM works through the list, checks to see if the class is already loaded and attempts to load it if not. It first tries to load from the local disk, using a platform-specific function provided by the browser. In our example, this is the way that all of the core Java classes are loaded. If the class name is not found on the local disk, the JVM again tries to retrieve the class by searching for it in the extension class path. If this also fails, the JVM tries to retrieve the class from the Web server, as in the case of the jamjar.examples.Button class.
Where Class Loaders Come From
The class loader is just another Java class, albeit one with a very specific function. An application can declare any number of class loaders, each of which could be targeted at specific class types. The same is not true of an applet. The security manager prevents an applet from creating its own class loader. Clearly, if an applet can somehow circumvent this limitation, it can subvert the class loading process and potentially take over the whole browser machine.
The JVM keeps track of which class loader was responsible for loading any particular class. It also keeps classes loaded by different applets separate from each other.
You can create a specific class loader for your own application, if you wish to do so. Java 2 has simplified the development process by creating a subclass of ClassLoader, called SecureClassLoader. The distinguishing feature of SecureClassLoader is that it associates a sandbox for each class that it loads, which determines what accesses and rights the class can exercise in the client system.
The Class File Verifier
At first sight, the job of the class file verifier may appear to be redundant. After all, isn’t bytecode only generated by the Java compiler? So, if it is not correctly formatted and valid, surely the compiler needs to be fixed, rather than having to go through the overhead of checking each time a program is run.
Java divides the world into two parts, since it considers the Java core classes shipped as part of the JVM and installed on the local system as trusted, and therefore not subject to verification prior to execution. Sometimes other classes on the local disk are considered trusted as well – detailed implementation varies between vendors. Everything else is untrusted and therefore must be checked by the class file verifier. As we have seen, these are also the classes that the applet class loader is responsible for fetching.
The class loading process in the example of the PointlessButton applet is illustrated in the figure:
Figure. Where the Class File Verifier Fits
You can already see that, for an applet, the class loader and the class file verifier need to operate as a team, if they are to succeed in their task of making sure that only valid, safe code is executed.
From a security point of view the accuracy of the job done by the class file verifier is critical. There are a large number of possible bytecode programs, and the class file verifier has the job of determining the subset of them that are safe to run, by testing against a set of rules. There is a further subset of these verifiable programs: programs that are the result of compiling a legal Java program. The rules in the class file verifier should aim to make the verifiable set as near as possible to the set of Java programs. This limits the scope for an attacker to create bytecode that subverts the safety features in Java and the protection of the security manager.
The Security Manager
The third component involved in loading and running a Java program is the security manager. This is similar to the class loader in that it is a Java class (java.lang.SecurityManager) that any application can extend for its own purpose.
The verified code is further subjected to run-time restrictions. The security manager is responsible for enforcing these restrictions. Any flaw in the coding of the security manager, or any failure by the core classes to invoke it, could compromise the ability to run untrusted code securely.
Prior to Java 2, SecurityManager was an abstract class and a concrete implementation had to be provided by the application manufacturer as part of the application. Although any application could implement SecurityManager, it was most commonly found when executing an applet, that is, within a Web browser. The security manager built into your browser was wholly responsible for enforcing the sandbox restrictions : the set of rules that controlled what things an applet was allowed to do on your browser machine.
In Java 2, SecurityManager has been modified: now it is not abstract, and can be instantiated or subclassed. The manufacturer now has an alternative. He can choose to use the policy-based security manager implementation provided with the Java 2 platform and supply policy information to be added to the policy database. The manufacturer can still provide his own security manager, if he so chooses, adding to or replacing function supplied by the Java 2 SecurityManager.