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.
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.
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).
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).
0 komentar pada Bytecode Verification
Posting Komentar