Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API

Newsletter sign-up

Sign up for our technology specific newsletters.

Enterprise Java
View all newsletters

Email Address:

Security and the class verifier

A look at the role played by the class verifier in the JVM's overall security model

This month's article continues the discussion of Java's security model begun in August's "Under the Hood." In that article, I gave a general 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 "Under the Hood," I examined the class loader architecture, another aspect of the JVM's built-in security mechanisms. This month I'll focus on the third prong of the JVM's security strategy: the class verifier.

The class-file verifier

Every Java virtual machine has a class-file verifier, which ensures that loaded class files have a proper internal structure. If the class-file verifier discovers a problem with a class file, it throws an exception. Because a class file is just a sequence of binary data, a virtual machine can't know whether a particular class file was generated by a well-meaning Java compiler or by shady crackers bent on compromising the integrity of the virtual machine. As a consequence, all JVM implementations have a class-file verifier that can be invoked on untrusted classes, to make sure the classes are safe to use.

One of the security goals that the class-file verifier helps achieve is program robustness. If a buggy compiler or savvy cracker generated a class file that contained a method whose bytecodes included an instruction to jump beyond the end of the method, that method could, if it were invoked, cause the virtual machine to crash. Thus, for the sake of robustness, it is important that the virtual machine verify the integrity of the bytecodes it imports.

Although Java virtual machine designers are allowed to decide when their virtual machines will perform these checks, many implementations will do most checking just after a class is loaded. Such a virtual machine analyzes bytecodes (and verifies their integrity) once, before they are ever executed. As part of its verification of bytecodes, the Java virtual machine makes sure all jump instructions -- for example, goto (jump always), ifeq (jump if top of stack zero), etc. -- cause a jump to another valid instruction in the bytecode stream of the method. As a consequence, the virtual machine need not check for a valid target every time it encounters a jump instruction as it executes bytecodes. In most cases, checking all bytecodes once before they are executed is a more efficient way to guarantee robustness than checking each bytecode instruction every time it is executed.

A class-file verifier that performs its checking as early as possible most likely operates in two distinct phases. During phase one, which takes place just after a class is loaded, the class-file verifier checks the internal structure of the class file, including verifying the integrity of the bytecodes it contains. During phase two, which takes place as bytecodes are executed, the class-file verifier confirms the existence of symbolically referenced classes, fields, and methods.

Phase one: Internal checks

During phase one, the class-file verifier checks everything that's possible to check in a class file by looking at only the class file itself (without examining any other classes or interfaces). Phase one of the class-file verifier makes sure the imported class file is properly formed, internally consistent, adheres to the constraints of the Java programming language, and contains bytecodes that will be safe for the Java virtual machine to execute. If the class-file verifier finds that any of these are not true, it throws an error, and the class file is never used by the program.

Checking format and internal consistency
Besides verifying the integrity of the bytecodes, the verifier performs many checks for proper class file format and internal consistency during phase one. For example, every class file must start with the same four bytes, the magic number: 0xCAFEBABE. The purpose of magic numbers is to make it easy for file parsers to recognize a certain type of file. Thus, the first thing a class-file verifier likely checks is that the imported file does indeed begin with 0xCAFEBABE.

The class-file verifier also checks to make sure the class file is neither truncated nor enhanced with extra trailing bytes. Although different class files can be different lengths, each individual component contained inside a class file indicates its length as well as its type. The verifier can use the component types and lengths to determine the correct total length for each individual class file. In this way, it can verify that the imported file has a length consistent with its internal contents.

The verifier also looks at individual components to make sure they are well-formed instances of their type of component. For example, a method descriptor (the method's return type and the number and types of its parameters) is stored in the class file as a string that must adhere to a certain context-free grammar. One of the checks the verifier performs on individual components is to make sure each method descriptor is a well-formed string of the appropriate grammar.

In addition, the class-file verifier checks that the class itself adheres to certain constraints placed on it by the specification of the Java programming language. For example, the verifier enforces the rule that all classes, except class Object, must have a superclass. Thus, the class-file verifier checks at runtime some of the Java language rules that should have been enforced at compile-time. Because the verifier has no way of knowing if the class file was generated by a benevolent, bug-free compiler, it checks each class file to make sure the rules are followed.

The bytecode verifier

Once the class-file verifier has successfully completed the checks for proper format and internal consistency, it turns its attention to the bytecodes. During this part of phase one, which is commonly called the "bytecode verifier," the Java virtual machine performs a data-flow analysis on the streams of bytecodes that represent the methods of the class. To understand the bytecode verifier, you need to understand a bit about bytecodes and frames.

1 | 2 | 3 | 4 | 5 |  Next >
Resources
  • The book The Java virtual machine Specification (http://www.aw.com/cp/lindholm-yellin.html), by Tim Lindholm and Frank Yellin (ISBN 0-201-63452-X), part of The Java Series (http://www.aw.com/cp/javaseries.html), from Addison-Wesley, is the definitive Java virtual machine reference.
  • Secure Computing with JavaNow and the Future (a whitepaper)http://www.javasoft.com/marketing/collateral/security.html
  • Applet Security FAQ
    http://www.javasoft.com/sfaq/
  • Low Level Security in Java, by Frank Yellin http://www.javasoft.com/sfaq/verifier.html
  • The Java Security Home Page
    http://www.javasoft.com/security/
  • See the Hostile Applets Home Page
    http://www.math.gatech.edu/~mladue/HostileApplets.html
  • The book Java SecurityHostile Applets, Holes, and Antidotes, by Dr. Gary McGraw and Ed Felton, gives a thorough analysis of security issues surrounding Java. http://www.rstcorp.com/java-security.html
  • An online version of the Java Language Specification is available at http://www.javasoft.com/docs/books/jls/html/index.html
  • Previous "Under The Hood" articles:
  • The Lean, Mean Virtual Machine -- Gives an introduction to the Java virtual machine.
  • The Java Class File Lifestyle -- Gives an overview of the Java class file, the file format into which all Java programs are compiled.
  • Java's Garbage- Collected Heap -- Gives an overview of garbage collection in general and the garbage-collected heap of the Java virtual machine in particular.
  • Bytecode Basics -- Introduces the bytecodes of the Java virtual machine, and discusses primitive types, conversion operations, and stack operations in particular.
  • Floating Point Arithmetic -- Describes the Java virtual machine's floating-point support and the bytecodes that perform floating point operations.
  • Logic and Arithmetic -- Describes the Java virtual machine's support for logical and integer arithmetic, and the related bytecodes.
  • Objects and Arrays -- Describes how the Java virtual machine deals with objects and arrays, and discusses the relevant bytecodes.
  • Exceptions -- Describes how the Java virtual machine deals with exceptions, and discusses the relevant bytecodes.
  • Try-Finally -- Describes how the Java virtual machine implements try-finally clauses, and discusses the relevant bytecodes.
  • Control Flow -- Describes how the Java virtual machine implements control flow and discusses the relevant bytecodes.
  • The Architecture of Aglets -- Describes the inner workings of aglets, IBM's autonomous Java-based software agent technology.
  • The Point of Aglets -- Analyzes the real-world utility of mobile agents such as aglets, IBM's autonomous Java- based software agent technology.
  • Method Invocation and Return -- Describes the four ways the Java virtual machine invokes methods, including the relevant bytecodes.
  • Thread Synchronization -- Shows how thread synchronization works in the Java virtual machine. Discusses the bytecodes for entering and exiting monitors.
  • Java's Security Architecture -- Gives an overview of the security model built into the JVM and looks at the JVM's built-in safety features.
  • Security and the Class Loader Architecture -- Describes class loaders and shows how the fit into Java's overall security model.