Sponsored Link •
|
Advertisement
|
Working in conjunction with the class loader, the class file verifier ensures that loaded class files have a proper internal structure and that they are consistent with each other. If the class file verifier discovers a problem with a class file, it throws an exception. Although compliant Java compilers should not generate malformed class files, a Java virtual machine can't tell how a particular class file was created. Because a class file is just a sequence of bytes, 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 Java virtual machine implementations have a class file verifier that can be invoked on class files, to make sure the types they define 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.
The class file verifier of the Java virtual machine does most checking before bytecodes are executed. Rather than checking every time it encounters a jump instruction as it executes bytecodes, for example, it 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 cause a jump to another valid instruction in the bytecode stream of the method. In most cases, checking all bytecodes once, before they are executed, is a more efficient way to guarantee robustness than checking every bytecode instruction every time it is executed.
The class file verifier operates in four distinct passes. During pass one, which takes place as a class is loaded, the class file verifier checks the internal structure of the class file to make sure it is safe to parse. During passes two and three, which take place during linking, the class file verifier makes sure the type data obeys the semantics of the Java programming language, including verifying the integrity of any bytecodes it contains. During pass four, which takes place as symbolic references are resolved in the process of dynamic linking, the class file verifier confirms the existence of symbolically referenced classes, fields, and methods.
During pass one, the class file verifier makes certain that the sequence of bytes it is about to attempt to
import as a type conforms to the basic structure of a Java class file. The verifier performs many checks
during this pass. For example, every class file must start with the same four bytes, the magic number:
0xCAFEBABE
. The purpose of the magic number is to make it easy for the class file
parser to reject files that were either damaged or were never intended to be class files in the first place.
Thus, the first thing a class file verifier likely checks is that the imported file does indeed begin with
0xCAFEBABE
. The verifier also makes sure the major and minor version numbers
declared in the class file are within the range supported by that implementation of the Java virtual machine.
Also during pass one, the class file verifier 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 point of pass one is to ensure the sequence of bytes that supposedly define a new type adheres sufficiently to the Java class file format to enable it to be parsed into implementation-specific internal data structures in the method area. Passes two, three, and four take place not on the binary data in the class file format, but on the implementation-specific data structures in the method area.
Pass two of the class file verifier performs checking that can be done without looking at the bytecodes and without looking at (or loading) any other types. During this pass, the verifier looks at individual components, to make sure they are well-formed instances of their type of component. For example, a method descriptor (its 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 check 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 upon 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. Also during pass two, the
verifier makes sure that final classes are not subclassed and final methods are not overridden. In addition, it
checks that constant pool entries are valid, and that all indexes into the constant pool refer to the correct
type of constant pool entry. Thus, the class file verifier checks at run-time 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.
Sponsored Links
|