information on Java security, see the sidebar "Plugs for Java's Security Holes" in this month's cover story.)
Java security relies on three prongs of defense: the Byte-Code Verifier, the Applet Class Loader, and the Security Manager. Together, these three prongs perform load and run-time checks to restrict file-system and network access, as well as restrict access to browser internals. Each of these prongs depends in some way on the others. Each part must do its job properly for the security model to function correctly.
The Security Triad
The figure
"Java Security Triad"
shows how the three prongs of defense fit into the Java framework. The Byte-Code Verifier is the first prong of the Java security model. When a Java s
ource program is compiled, it's converted to platform-independent Java byte code. The Verifier then checks that the untrusted outside code "plays by the rules" before it's allowed to run.
The Verifier checks byte code at a number of different levels. The simplest test ensures that a
.class
file (i.e., a byte-code file) has the correct format. On a less basic level, the Verifier applies a built-in theorem prover to each method. The theorem prover helps ensure that byte code does not forge pointers, violate access restrictions, or access objects using incorrect type information. The verification process, in concert with the definition of the Java language, helps to establish a base set of security guarantees.
Java's second prong of security de-fense is the Applet Class Loader. Typically supplied by a browser vendor, it loads all applets and the classes that they reference. When an applet is loaded from the network, the Applet Class Loader receives the binary data and instantiates it as a new cl
ass. The Class Loader determines when and how an applet can add classes to a running Java environment. Part of the Class Loader's job is to make sure that the applet doesn't install code that replaces important components of the Java run-time environment.
In general, a running Java environment can have many active Class Loaders, each defining its own namespace.
Namespaces
allow Java classes to be separated into distinct kinds, according to where they originate. In other words, a namespace is a type-safe portion of memory with classes that are associated with a specific Class Loader.
The third prong of the Java security model is the Security Manager, which restricts the ways in which an applet can use visible interfaces. Thus, the Security Manager implements a good portion of the entire security model. It's a single module that performs run-time checks on dangerous methods, such as those for file or network access or those that define new Class Loaders.
Code in the Java library consults th
e Security Manager whenever a dangerous operation is about to be attempted. The Security Manager then has a chance to veto the operation by generating a Security Exception. Decisions made by the Security Manager take into account which Class Loader loaded the requesting class. Built-in classes are given more privilege than classes that have been loaded over the network (e.g., applets).
The three parts of the Java security model were created to enforce
type safety
, which means that a program can perform particular operations only on particular kinds of objects. Therefore, Java programs are prevented from accessing memory in inappropriate ways.
More specifically, every piece of memory is part of some Java object, and each object has some class. For example, a calendar management applet might use such classes as Date, Appointment, Alarm, and GroupCalendar. Each class defines a specific set of operations that are allowed to operate on objects of that class. In the calendar management example, the
Alarm class might define a
turnOn
operation, but the Date class would not allow
turnOn
to occur.
Why Type Safety Matters
To understand why type safety matters, consider the following, slightly contrived, example. The calendar management applet mentioned above defines a class Alarm, which is represented in memory, as shown in the figure
"Type Safety"
. Alarm defines an operation
turnOn
, which sets the first field to true. The Java run-time library defines another class called Applet, whose memory layout is shown in the figure. Note that the first field of Applet is
fileAccessAllowed
, which says whether or not the applet is allowed to access files on the hard disk.
Now suppose that a program tries to apply the
turnOn
operation to an Applet object. If the
turnOn
operation is permitted, the program sets the first field of the object to true. Unfortunately, since the target object is really of type Applet, sett
ing the first field to true allows the applet to access the file system. The applet is then allowed -- incorrectly -- to modify and even delete files.
How Java Enforces Type Safety
Java labels every object by associating a class tag with it. One simple way to enforce type safety would be to check an object's type tag before every operation on it to make sure that the object's class allows such an operation. This approach is called
dynamic type checking
.
Although this scheme works, it's inefficient. Programs end up running slowly because the system spends a lot of time checking class tags. To improve performance, Java uses static type checking, which is more complicated but more efficient than dynamic type checking.
Static type checking
is where the Java system looks at a program before it runs and carefully deduces the results of the tag-checking operations. If Java can figure out that a particular tag-checking operation will always succeed, then there's no reaso
n to do it. The check can safely be removed, thus speeding up the program.
Java's designers carefully crafted the Java language and byte-code formats to facilitate static type checking. Each piece of byte code is a binary representation of an assembly-like language with op codes and operands.
But Java op codes always take type-specific arguments. There are no "generic" operands that take multiple types in the same operand position, as is the case with processor assembly languages.
This, and other properties of byte code, make static type checking easier to implement. The Byte-Code Verifier is an effective static type checker that eliminates almost all the tag-checking operations from Java programs. The result is a program that's type-safe but that runs quite efficiently.
Type Confusion
There is only one problem with Java's static type-checking strategy: It's complicated. Although Java's designers obviously got the overall strategy right, a great many details have to be cor
rect for type safety to be enforced. An error in any of these details leaves a tiny, albeit crucial, hole in Java's type-safety armor.
A clever cracker who finds such a hole can launch a
type-confusion attack
. This is done with a Java applet carefully designed to leverage a tiny type-enforcement hole into a complete system penetration. The attacker can set up a situation like the aforementioned Alarm/Applet example, in which the program has one type of object but the Java system thinks the object has some other type.
Because the Verifier normally prohibits such actions, type-confusion errors are usually the result of bugs in the Java implementation. It is hoped that such problems will disappear as the implementation is debugged and refined.
illustration_link (29 Kbytes)

Java performs several safety checks before a downloaded applet can execute.
illustration_link (14 Kbytes)

Java ensures that malicious programs can't gain access to system resources.
http://www.rstcorp.com/~gem
. Edward Felten, Ph.D., is an assistant
professor of computer science at Princeton University. He can be reached at
http://www.cs.princeton.edu/~felten
. Portions of this article are taken from the authors' book Java Security: Hostile Applets, Holes, and Antidotes (John Wiley and Sons, 1996).