Home > Articles > Programming > Java

  • Print
  • + Share This
This chapter is from the book

Bytecode Verification

When a class loader presents the bytecodes of a newly loaded Java platform class to the virtual machine, these bytecodes are first inspected by a verifier. The verifier checks that the instructions cannot perform actions that are obviously damaging. All classes except for system classes are verified. You can, however, deactivate verification with the undocumented -noverify option.

For example,

java -noverify Hello

Here are some of the checks that the verifier carries out:

  • Variables are initialized before they are used.
  • Method calls match the types of object references.
  • Rules for accessing private data and methods are not violated.
  • Local variable accesses fall within the runtime stack.
  • The runtime stack does not overflow.

If any of these checks fails, then the class is considered corrupted and will not be loaded.

This strict verification is an important security consideration. Accidental errors, such as uninitialized variables, can easily wreak havoc if they are not caught. More important, in the wide open world of the Internet, you must be protected against malicious programmers who create evil effects on purpose. For example, by modifying values on the runtime stack or by writing to the private data fields of system objects, a program can break through the security system of a browser.

You might wonder, however, why a special verifier checks all these features. After all, the compiler would never allow you to generate a class file in which an uninitialized variable is used or in which a private data field is accessed from another class. Indeed, a class file generated by a compiler for the Java programming language always passes verification. However, the bytecode format used in the class files is well documented, and it is an easy matter for someone with some experience in assembly programming and a hex editor to manually produce a class file that contains valid but unsafe instructions for the Java virtual machine. Once again, keep in mind that the verifier is always guarding against maliciously altered class files, not just checking the class files produced by a compiler.

Here's an example of how to construct such an altered class file. We start with the program VerifierTest.java of Listing 9-3. This is a simple program that calls a method and displays the method result. The program can be run both as a console program and as an applet. The fun method itself just computes 1 + 2.

static int fun()
{
   int m;
   int n;
   m = 1;
   n = 2;
   int r = m + n;
   return r;
}

As an experiment, try to compile the following modification of this program:

static int fun()
{
   int m = 1;
   int n;
   m = 1;
   m = 2;
   int r = m + n;
   return r;
}

In this case, n is not initialized, and it could have any random value. Of course, the compiler detects that problem and refuses to compile the program. To create a bad class file, we have to work a little harder. First, run the javap program to find out how the compiler translates the fun method. The command

javap -c VerifierTest

shows the bytecodes in the class file in mnemonic form.

Method int fun()
   0 iconst_1
   1 istore_0
   2 iconst_2
   3 istore_1
   4 iload_0
   5 iload_1
   6 iadd
   7 istore_2
   8 iload_2
   9 ireturn

We use a hex editor to change instruction 3 from istore_1 to istore_0. That is, local variable 0 (which is m) is initialized twice, and local variable 1 (which is n) is not initialized at all. We need to know the hexadecimal values for these instructions. These values are readily available from The Java Virtual Machine Specification, 2nd ed., by Tim Lindholm and Frank Yellin (Prentice Hall PTR 1999).

   0 iconst_1 04
   1 istore_0 3B
   2 iconst_2 05
   3 istore_1 3C
   4 iload_0  1A
   5 iload_1  1B
   6 iadd     60
   7 istore_2 3D
   8 iload_2  1C
   9 ireturn  AC

You can use any hex editor to carry out the modification. In Figure 9-4, you see the class file VerifierTest.class loaded into the Gnome hex editor, with the bytecodes of the fun method highlighted.

Figure 9-4

Figure 9-4 Modifying bytecodes with a hex editor

Change 3C to 3B and save the class file. Then try running the VerifierTest program. You get an error message:

Exception in thread "main" java.lang.VerifyError: (class: VerifierTest, method:fun signature:
()I) Accessing value from uninitialized register 1

That is good—the virtual machine detected our modification.

Now run the program with the -noverify (or -Xverify:none) option.

java -noverify VerifierTest

The fun method returns a seemingly random value. This is actually 2 plus the value that happened to be stored in the variable n, which never was initialized. Here is a typical printout:

1 + 2 == 15102330

To see how browsers handle verification, we wrote this program to run either as an application or an applet. Load the applet into a browser, using a file URL such as

file:///C:/CoreJavaBook/v2ch9/VerifierTest/VerifierTest.html

You then see an error message displayed indicating that verification has failed (see Figure 9-5).

Figure 9-5

Figure 9-5 Loading a corrupted class file raises a method verification error

Listing 9-3. VerifierTest.java

 
 1. import java.applet.*;
 2. import java.awt.*;
 3.
 4. /**
 5.  * This application demonstrates the bytecode verifier of the virtual machine. If you use a
 6.  * hex editor to modify the class file, then the virtual machine should detect the tampering.
 7.  * @version 1.00 1997-09-10
 8.  * @author Cay Horstmann
 9.  */
10. public class VerifierTest extends Applet
11. {
12.    public static void main(String[] args)
13.    {
14.       System.out.println("1 + 2 == " + fun());
15.    }
16.
17.    /**
18.     * A function that computes 1 + 2
19.     * @return 3, if the code has not been corrupted
20.     */
21.    public static int fun()
22.    {
23.       int m;
24.       int n;
25.       m = 1;
26.       n = 2;
27.       // use hex editor to change to "m = 2" in class file
28.       int r = m + n;
29.       return r;
30.    }
31.
32.    public void paint(Graphics g)
33.    {
34.       g.drawString("1 + 2 == " + fun(), 20, 20);
35.    }
36. }
  • + Share This
  • 🔖 Save To Your Account