Home > java > Crack The Verifier

Crack The Verifier

10 novembre 2010

Translate in English with Google Translate
Tempo fa Sun Microsystem lanciò una sfida dal nome Crack The Verifier rivolta alla comunità per testare il Verifier di Java 1.6 Mustang. In questo articolo intendo mostrare quali strumenti poter usare per “provare a rompere” il Verifier. In un articolo precedente ho fatto una introduzione al Verifier, quindi adesso passo agli esempi.

Casi di errore
Il riconoscimento di un errore ad opera del Verifier da luogo alla propagazione di java.lang.VerifyError e tale errore viene solitamente propagato in caso di un baco del compilatore oppure di un tentativo di hacking del file class.

Scegliano il secondo caso e creiamo un file class personalizzato.
In questo modo, possiamo seguire 2 strade:

  1. creare un nuovo file class utilizzando il progetto jasmin
  2. modificare un file class esistenze utilizzando un editor esadecimanle oppure il progetto jasmin + kimera

Nel primo caso: dobbiamo avere una conoscenza di base su le istruzioni macchina, del bytecode e di jasmin
Nel secondo caso: modificare un file usando un editor esadecimale richiede molto impegno mentre invece usare i progetti jasmin e kimera ci consente di velocizzare le nostre prove.

In base a questi presupposti, stabilisco il mio piano di azione:

  1. creo una semplice classe java
  2. compilo la classe usando il JDK di Oracle
  3. disassemblo il bytecode in codice jasmin usando il progetto kimera ed ottengo un file jasmn
  4. modifico il file jasmin inserendo gli opcode di hacking
  5. compilo il file jasmin usando l’assemblatore jasmin ed ottengo il nuovo file class
  6. eseguo il bytecode generato per testare il Verifier

Breve introduzione degli strumenti da usare

  • jasmin: è un progetto open source sviluppato da Jon Meyer e Troy Downing che a partire da un linguaggio simile al linguaggio della JVM consente di generare bytecode .
  • kimera: è un disassemblatore sviluppato dall’Università di Washington che a partire da file class consente di ottenenere un assemblato jasmin

Iniziamo con l’esempio
Partiamo da una classe che ci restituisce l’hashCode di una stringa:

class Prova{
        public static void main(String [] arg) {
            System.out.println( "hello".hashCode() );
        }
}

Adesso vediamo come risulta il codice della JVM usando il disassemblatore Sun javap -C

$ /opt/jdk1.6.0_21/bin/javap -c  Prova
Compiled from "Prova.java"
class Prova extends java.lang.Object{
Prova();
  Code:
   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."":()V
   4:	return

public static void main(java.lang.String[]);
  Code:
   0:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:	ldc	#3; //String hello
   5:	invokevirtual	#4; //Method java/lang/String.hashCode:()I
   8:	invokevirtual	#5; //Method java/io/PrintStream.println:(I)V
   11:	return

}

Adesso vediamo il codice esadecimale. Su Windows possiamo usare ultraedit che prevede un editor esadecimale.

Mentre su linux possiamo usare questa istruzione:

hexdump -C  Prova.class
00000000  ca fe ba be 00 00 00 32  00 23 0a 00 07 00 10 09  |.......2.#......|
00000010  00 11 00 12 08 00 13 0a  00 14 00 15 0a 00 16 00  |................|
00000020  17 07 00 18 07 00 19 01  00 06 3c 69 6e 69 74 3e  |..........|
00000030  01 00 03 28 29 56 01 00  04 43 6f 64 65 01 00 0f  |...()V...Code...|
00000040  4c 69 6e 65 4e 75 6d 62  65 72 54 61 62 6c 65 01  |LineNumberTable.|
00000050  00 04 6d 61 69 6e 01 00  16 28 5b 4c 6a 61 76 61  |..main...([Ljava|
00000060  2f 6c 61 6e 67 2f 53 74  72 69 6e 67 3b 29 56 01  |/lang/String;)V.|
00000070  00 0a 53 6f 75 72 63 65  46 69 6c 65 01 00 0a 50  |..SourceFile...P|
00000080  72 6f 76 61 2e 6a 61 76  61 0c 00 08 00 09 07 00  |rova.java.......|
00000090  1a 0c 00 1b 00 1c 01 00  05 68 65 6c 6c 6f 07 00  |.........hello..|
000000a0  1d 0c 00 1e 00 1f 07 00  20 0c 00 21 00 22 01 00  |........ ..!."..|
000000b0  05 50 72 6f 76 61 01 00  10 6a 61 76 61 2f 6c 61  |.Prova...java/la|
000000c0  6e 67 2f 4f 62 6a 65 63  74 01 00 10 6a 61 76 61  |ng/Object...java|
000000d0  2f 6c 61 6e 67 2f 53 79  73 74 65 6d 01 00 03 6f  |/lang/System...o|
000000e0  75 74 01 00 15 4c 6a 61  76 61 2f 69 6f 2f 50 72  |ut...Ljava/io/Pr|
000000f0  69 6e 74 53 74 72 65 61  6d 3b 01 00 10 6a 61 76  |intStream;...jav|
00000100  61 2f 6c 61 6e 67 2f 53  74 72 69 6e 67 01 00 08  |a/lang/String...|
00000110  68 61 73 68 43 6f 64 65  01 00 03 28 29 49 01 00  |hashCode...()I..|
00000120  13 6a 61 76 61 2f 69 6f  2f 50 72 69 6e 74 53 74  |.java/io/PrintSt|
00000130  72 65 61 6d 01 00 07 70  72 69 6e 74 6c 6e 01 00  |ream...println..|
00000140  04 28 49 29 56 00 20 00  06 00 07 00 00 00 00 00  |.(I)V. .........|
00000150  02 00 00 00 08 00 09 00  01 00 0a 00 00 00 1d 00  |................|
00000160  01 00 01 00 00 00 05 2a  b7 00 01 b1 00 00 00 01  |.......*........|
00000170  00 0b 00 00 00 06 00 01  00 00 00 01 00 09 00 0c  |................|
00000180  00 0d 00 01 00 0a 00 00  00 28 00 02 00 01 00 00  |.........(......|
00000190  00 0c b2 00 02 12 03 b6  00 04 b6 00 05 b1 00 00  |................|
000001a0  00 01 00 0b 00 00 00 0a  00 02 00 00 00 04 00 0b  |................|
000001b0  00 05 00 01 00 0e 00 00  00 02 00 0f              |............|
000001bc

Notiamo ad inizio file esadecimale i primi 4 bytes “ca fe ba be” che riportano correttamente il magic.
Adesso modifichiamo il bytecode. Come?
Usando il progetto kimera possiamo disassemblare il bytecode ed ottenere il codice jasmin.

Per fare questo andiamo sul progetto kimera e facciamo upload del nostro file class Prova.class.

Kimera Disassembler

Kimera Disassembler

ed otteniamo questo codice jasmin:

.class  synchronized Prova
.super java/lang/Object

.method ()V
    .limit locals 1
    .limit stack 1

    aload_0
    invokenonvirtual	java/lang/Object.()V
    return
.end method

.method public static main([Ljava/lang/String;)V
    .limit locals 1
    .limit stack 2

    getstatic	java/lang/System/out Ljava/io/PrintStream;
    ldc	"hello"
    invokevirtual	java/lang/String.hashCode()I
    invokevirtual	java/io/PrintStream.println(I)V
    return
.end method

L’istruzione ldc “hello” viene memorizzato nello stack una stringa “hello”.
Adesso proviamo a modificare il codice jasmin provando ad invocare il metodo hashCode() su un tipo primitivo int. Per fare questo inseriamo nello stack un intero e per fare questo sostituiamo a ldc hello -> ldc 9:

.class  synchronized Prova
.super java/lang/Object

.method ()V
    .limit locals 1
    .limit stack 1

    aload_0
    invokenonvirtual	java/lang/Object.()V
    return
.end method

.method public static main([Ljava/lang/String;)V
    .limit locals 1
    .limit stack 2

        getstatic       java/lang/System/out Ljava/io/PrintStream;
        ldc     9
        invokevirtual   java/lang/String.hashCode()I
        invokevirtual   java/io/PrintStream.println(I)V
        return
.end method

Salviamo il codice jasmin in un file dal nome Prova.jasmin e compiliamolo usando la libreria jasmin precedentemente scaricata. Otteniamo il nuovo file class che sostituirà il precedente:

/opt/jdk1.6.0_21/bin/java -jar /opt/jasmin-2.4/jasmin.jar Prova.jasmin
Generated: Prova.class

Adesso vediamo come risulta il codice della JVM usando il disassemblatore Sun javap -C

$ /opt/jdk1.6.0_21/bin/javap -c  Prova
Compiled from "Prova.jasmin"
class Prova extends java.lang.Object{
Prova();
  Code:
   0:	aload_0
   1:	invokespecial	#24; //Method java/lang/Object."":()V
   4:	return

public static void main(java.lang.String[]);
  Code:
   0:	getstatic	#17; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:	ldc	#2; //int 9
   5:	invokevirtual	#27; //Method java/lang/String.hashCode:()I
   8:	invokevirtual	#11; //Method java/io/PrintStream.println:(I)V
   11:	return

}

Adesso possiamo finalmente testare il bytecode generato e per fare questo eseguiamo la classe usando l’opzione -verify che forza la verifica. Abbiamo questo messaggio:

$ /opt/jdk1.6.0_21/bin/java -verify  Prova
Exception in thread "main" java.lang.VerifyError:
(class: Prova, method: main signature:
([Ljava/lang/String;)V) Expecting to find object/array on stack
Could not find the main class: Prova.  Program will exit.

Il Verify si è accorto dell’errore! Non è possibile richiamare il metodo hashCode() su un int, infatti non ha trovato un’oggetto nello stack.

Adesso invece eseguiamo la classe con l’opzione -noverify per impedire il controllo ad opera del Verify ed abbiamo questo output:

$ /opt/jdk1.6.0_21/bin/java -noverify  Prova
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0xb7888430, pid=7961, tid=3074063248
#
# JRE version: 6.0_21-b06
# Java VM: Java HotSpot(TM) Server VM (17.0-b16 mixed mode linux-x86 )
# Problematic frame:
# V  [libjvm.so+0x4e0430]
#
# An error report file with more information is saved as:
# /media/_/ARCHIVIO/download_web/wordpress/Verifier/hs_err_pid7961.log
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#
Abortito

Si è verificato un crash della JVM. Vediamo cosa dice il report del crash della JVM hs_err_pid7961.log:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0xb7888430, pid=7961, tid=3074063248
#
# JRE version: 6.0_21-b06
# Java VM: Java HotSpot(TM) Server VM (17.0-b16 mixed mode linux-x86 )
# Problematic frame:
# V  [libjvm.so+0x4e0430]
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#

---------------  T H R E A D  ---------------

Current thread (0x0825b400):  JavaThread "main" [_thread_in_vm, id=7962, stack(0xb7357000,0xb73a8000)]

siginfo:si_signo=SIGSEGV: si_errno=0, si_code=1 (SEGV_MAPERR), si_addr=0x0000000d

Registers:
EAX=0x00000009, EBX=0xb7b22748, ECX=0xb73a6eb8, EDX=0x0825a9c0
ESP=0xb73a6eb0, EBP=0xb73a6ed8, ESI=0x0825b400, EDI=0x0825b878
EIP=0xb7888430, CR2=0x0000000d, EFLAGS=0x00010206

Top of Stack: (sp=0xb73a6eb0)
0xb73a6eb0:   0000001d b73a6eb8 00000154 0825b8ac
0xb73a6ec0:   0825b8a8 0825b8a4 0825b8a0 b7b22748
0xb73a6ed0:   00000300 0825b87c b73a6f18 b788811a
0xb73a6ee0:   b73a6fc0 0825b878 0825b87c 00000300
0xb73a6ef0:   0825b400 00000000 b760d6bb b78880cd
0xb73a6f00:   b73a6fa0 0825b508 b73a6f28 b7b22748
0xb73a6f10:   000000b6 0825b508 b73a7028 b773651e
0xb73a6f20:   b73a6fc0 0825b878 0825b87c 00000300

Instructions: (pc=0xb7888430)
0xb7888420:   45 0c 31 ff 89 4d dc 85 c0 74 08 8b 7d 0c 8b 07
0xb7888430:   8b 78 04 85 ff 75 3c 8b 45 dc c7 00 00 00 00 00

Stack: [0xb7357000,0xb73a8000],  sp=0xb73a6eb0,  free space=13fb73a6830k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [libjvm.so+0x4e0430]
V  [libjvm.so+0x4e011a]
V  [libjvm.so+0x38e51e]
j  Prova.main([Ljava/lang/String;)V+5
v  ~StubRoutines::call_stub
V  [libjvm.so+0x395920]
V  [libjvm.so+0x5793a8]
V  [libjvm.so+0x39577f]
V  [libjvm.so+0x3c7c23]
V  [libjvm.so+0x3b7e7c]
C  [java+0x1b98]  JavaMain+0x2c8
C  [libpthread.so.0+0x651f]

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  Prova.main([Ljava/lang/String;)V+5
v  ~StubRoutines::call_stub

---------------  P R O C E S S  ---------------

Java Threads: ( => current thread )
  0x9028c000 JavaThread "Low Memory Detector" daemon [_thread_blocked, id=7971, stack(0x8fba2000,0x8fbf3000)]
  0x9028a800 JavaThread "CompilerThread1" daemon [_thread_blocked, id=7970, stack(0x8fbf3000,0x8fc74000)]
  0x90288800 JavaThread "CompilerThread0" daemon [_thread_blocked, id=7969, stack(0x8fc74000,0x8fcf5000)]
  0x90286c00 JavaThread "Signal Dispatcher" daemon [_thread_blocked, id=7968, stack(0x8fcf5000,0x8fd46000)]
  0x90277400 JavaThread "Finalizer" daemon [_thread_blocked, id=7967, stack(0x8ff46000,0x8ff97000)]
  0x90275c00 JavaThread "Reference Handler" daemon [_thread_blocked, id=7966, stack(0x8ff97000,0x8ffe8000)]
=>0x0825b400 JavaThread "main" [_thread_in_vm, id=7962, stack(0xb7357000,0xb73a8000)]

Other Threads:
  0x90273400 VMThread [stack: 0x8ffe8000,0x90069000] [id=7965]
  0x9028e000 WatcherThread [stack: 0x8fb21000,0x8fba2000] [id=7972]

VM state:not at safepoint (normal execution)

VM Mutex/Monitor currently owned by a thread: None

Heap
 PSYoungGen      total 9472K, used 325K [0xa98f0000, 0xaa380000, 0xb4240000)
  eden space 8128K, 4% used [0xa98f0000,0xa9941568,0xaa0e0000)
  from space 1344K, 0% used [0xaa230000,0xaa230000,0xaa380000)
  to   space 1344K, 0% used [0xaa0e0000,0xaa0e0000,0xaa230000)
 PSOldGen        total 21632K, used 0K [0x94640000, 0x95b60000, 0xa98f0000)
  object space 21632K, 0% used [0x94640000,0x94640000,0x95b60000)
 PSPermGen       total 16384K, used 1691K [0x90640000, 0x91640000, 0x94640000)
  object space 16384K, 10% used [0x90640000,0x907e6d38,0x91640000)

Dynamic libraries:
008e8000-00908000 r-xp 00000000 08:07 1566904    /lib/ld-2.9.so
00909000-0090a000 r-xp 00020000 08:07 1566904    /lib/ld-2.9.so
0090a000-0090b000 rwxp 00021000 08:07 1566904    /lib/ld-2.9.so
0090d000-00a7b000 r-xp 00000000 08:07 1566906    /lib/libc-2.9.so
00a7b000-00a7d000 r-xp 0016e000 08:07 1566906    /lib/libc-2.9.so
00a7d000-00a7e000 rwxp 00170000 08:07 1566906    /lib/libc-2.9.so
00a7e000-00a81000 rwxp 00a7e000 00:00 0
00a83000-00aaa000 r-xp 00000000 08:07 1566968    /lib/libm-2.9.so
00aaa000-00aab000 r-xp 00026000 08:07 1566968    /lib/libm-2.9.so
00aab000-00aac000 rwxp 00027000 08:07 1566968    /lib/libm-2.9.so
00aae000-00ab1000 r-xp 00000000 08:07 1567293    /lib/libdl-2.9.so
00ab1000-00ab2000 r-xp 00002000 08:07 1567293    /lib/libdl-2.9.so
00ab2000-00ab3000 rwxp 00003000 08:07 1567293    /lib/libdl-2.9.so
00ab5000-00acb000 r-xp 00000000 08:07 1567309    /lib/libpthread-2.9.so
00acb000-00acc000 r-xp 00015000 08:07 1567309    /lib/libpthread-2.9.so
00acc000-00acd000 rwxp 00016000 08:07 1567309    /lib/libpthread-2.9.so
00acd000-00acf000 rwxp 00acd000 00:00 0
00bea000-00bf2000 r-xp 00000000 08:07 1567350    /lib/librt-2.9.so
00bf2000-00bf3000 r-xp 00007000 08:07 1567350    /lib/librt-2.9.so
00bf3000-00bf4000 rwxp 00008000 08:07 1567350    /lib/librt-2.9.so
073f6000-0740c000 r-xp 00000000 08:07 1567359    /lib/libnsl-2.9.so
0740c000-0740d000 r-xp 00016000 08:07 1567359    /lib/libnsl-2.9.so
0740d000-0740e000 rwxp 00017000 08:07 1567359    /lib/libnsl-2.9.so
0740e000-07410000 rwxp 0740e000 00:00 0
08048000-08052000 r-xp 00000000 08:07 907914     /opt/jdk1.6.0_21/bin/java
08052000-08053000 rwxp 00009000 08:07 907914     /opt/jdk1.6.0_21/bin/java
08256000-08277000 rwxp 08256000 00:00 0          [heap]
8fb21000-8fb22000 ---p 8fb21000 00:00 0
8fb22000-8fba2000 rwxp 8fb22000 00:00 0
8fba2000-8fba5000 ---p 8fba2000 00:00 0
8fba5000-8fbf3000 rwxp 8fba5000 00:00 0
8fbf3000-8fbf6000 ---p 8fbf3000 00:00 0
8fbf6000-8fc74000 rwxp 8fbf6000 00:00 0
8fc74000-8fc77000 ---p 8fc74000 00:00 0
8fc77000-8fcf5000 rwxp 8fc77000 00:00 0
8fcf5000-8fcf8000 ---p 8fcf5000 00:00 0
8fcf8000-8fd46000 rwxp 8fcf8000 00:00 0
8fd46000-8ff46000 r-xp 00000000 08:07 1501614    /usr/lib/locale/locale-archive
8ff46000-8ff49000 ---p 8ff46000 00:00 0
8ff49000-8ff97000 rwxp 8ff49000 00:00 0
8ff97000-8ff9a000 ---p 8ff97000 00:00 0
8ff9a000-8ffe8000 rwxp 8ff9a000 00:00 0
8ffe8000-8ffe9000 ---p 8ffe8000 00:00 0
8ffe9000-90069000 rwxp 8ffe9000 00:00 0
90069000-90200000 r-xs 03014000 08:07 1526537    /opt/jdk1.6.0_21/jre/lib/rt.jar
90200000-902a2000 rwxp 90200000 00:00 0
902a2000-90300000 ---p 902a2000 00:00 0
90321000-90355000 rwxp 90321000 00:00 0
90355000-90356000 ---p 90355000 00:00 0
90356000-903d6000 rwxp 90356000 00:00 0
903d6000-903d7000 ---p 903d6000 00:00 0
903d7000-9045f000 rwxp 903d7000 00:00 0
9045f000-90477000 rwxp 9045f000 00:00 0
90477000-90482000 rwxp 90477000 00:00 0
90482000-90521000 rwxp 90482000 00:00 0
90521000-90529000 rwxp 90521000 00:00 0
90529000-90541000 rwxp 90529000 00:00 0
90541000-9054c000 rwxp 90541000 00:00 0
9054c000-905ea000 rwxp 9054c000 00:00 0
905ea000-905f0000 rwxp 905ea000 00:00 0
905f0000-9063f000 rwxp 905f0000 00:00 0
9063f000-91640000 rwxp 9063f000 00:00 0
91640000-94640000 rwxp 91640000 00:00 0
94640000-95b60000 rwxp 94640000 00:00 0
95b60000-a98f0000 rwxp 95b60000 00:00 0
a98f0000-aa380000 rwxp a98f0000 00:00 0
aa380000-b4240000 rwxp aa380000 00:00 0
b4244000-b424d000 rwxp b4244000 00:00 0
b424d000-b4304000 rwxp b424d000 00:00 0
b4304000-b4544000 rwxp b4304000 00:00 0
b4544000-b7304000 rwxp b4544000 00:00 0
b7304000-b730f000 r-xp 00000000 08:07 1566905    /lib/libnss_files-2.9.so
b730f000-b7310000 r-xp 0000a000 08:07 1566905    /lib/libnss_files-2.9.so
b7310000-b7311000 rwxp 0000b000 08:07 1566905    /lib/libnss_files-2.9.so
b7321000-b7330000 r-xp 00000000 08:07 1144824    /opt/jdk1.6.0_21/jre/lib/i386/libzip.so
b7330000-b7332000 rwxp 0000e000 08:07 1144824    /opt/jdk1.6.0_21/jre/lib/i386/libzip.so
b7332000-b7355000 r-xp 00000000 08:07 1144822    /opt/jdk1.6.0_21/jre/lib/i386/libjava.so
b7355000-b7357000 rwxp 00023000 08:07 1144822    /opt/jdk1.6.0_21/jre/lib/i386/libjava.so
b7357000-b735a000 ---p b7357000 00:00 0
b735a000-b73a8000 rwxp b735a000 00:00 0
b73a8000-b7ad4000 r-xp 00000000 08:07 1163883    /opt/jdk1.6.0_21/jre/lib/i386/server/libjvm.so
b7ad4000-b7b27000 rwxp 0072c000 08:07 1163883    /opt/jdk1.6.0_21/jre/lib/i386/server/libjvm.so
b7b27000-b7f48000 rwxp b7b27000 00:00 0
b7f48000-b7f4f000 r-xp 00000000 08:07 1640297    /opt/jdk1.6.0_21/jre/lib/i386/jli/libjli.so
b7f4f000-b7f51000 rwxp 00006000 08:07 1640297    /opt/jdk1.6.0_21/jre/lib/i386/jli/libjli.so
b7f55000-b7f5d000 rwxs 00000000 08:07 631690     /tmp/hsperfdata_peppe/7961
b7f5d000-b7f63000 r-xp 00000000 08:07 199645     /opt/jdk1.6.0_21/jre/lib/i386/native_threads/libhpi.so
b7f63000-b7f64000 rwxp 00006000 08:07 199645     /opt/jdk1.6.0_21/jre/lib/i386/native_threads/libhpi.so
b7f64000-b7f65000 rwxp b7f64000 00:00 0
b7f65000-b7f66000 r-xp b7f65000 00:00 0
b7f66000-b7f71000 r-xp 00000000 08:07 228867     /opt/jdk1.6.0_21/jre/lib/i386/libverify.so
b7f71000-b7f72000 rwxp 0000b000 08:07 228867     /opt/jdk1.6.0_21/jre/lib/i386/libverify.so
b7f72000-b7f73000 rwxp b7f72000 00:00 0
b7f73000-b7f74000 r-xp b7f73000 00:00 0          [vdso]
bf85f000-bf874000 rwxp bffeb000 00:00 0          [stack]

VM Arguments:
jvm_args: -Xverify:none
java_command: Prova
Launcher Type: SUN_STANDARD

Environment Variables:
PATH=/usr/lib/qt-3.3/bin:/usr/kerberos/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/peppe/bin
USERNAME=peppe
LD_LIBRARY_PATH=/opt/jdk1.6.0_21/jre/lib/i386/server:/opt/jdk1.6.0_21/jre/lib/i386:/opt/jdk1.6.0_21/jre/../lib/i386
SHELL=/bin/bash
DISPLAY=:0.0

Signal Handlers:
SIGSEGV: [libjvm.so+0x6a9eb0], sa_mask[0]=0x7ffbfeff, sa_flags=0x10000004
SIGBUS: [libjvm.so+0x6a9eb0], sa_mask[0]=0x7ffbfeff, sa_flags=0x10000004
SIGFPE: [libjvm.so+0x578180], sa_mask[0]=0x7ffbfeff, sa_flags=0x10000004
SIGPIPE: [libjvm.so+0x578180], sa_mask[0]=0x7ffbfeff, sa_flags=0x10000004
SIGXFSZ: [libjvm.so+0x578180], sa_mask[0]=0x7ffbfeff, sa_flags=0x10000004
SIGILL: [libjvm.so+0x578180], sa_mask[0]=0x7ffbfeff, sa_flags=0x10000004
SIGUSR1: SIG_DFL, sa_mask[0]=0x00000000, sa_flags=0x00000000
SIGUSR2: [libjvm.so+0x57adc0], sa_mask[0]=0x00000000, sa_flags=0x10000004
SIGHUP: [libjvm.so+0x57aaf0], sa_mask[0]=0x7ffbfeff, sa_flags=0x10000004
SIGINT: [libjvm.so+0x57aaf0], sa_mask[0]=0x7ffbfeff, sa_flags=0x10000004
SIGTERM: [libjvm.so+0x57aaf0], sa_mask[0]=0x7ffbfeff, sa_flags=0x10000004
SIGQUIT: [libjvm.so+0x57aaf0], sa_mask[0]=0x7ffbfeff, sa_flags=0x10000004

---------------  S Y S T E M  ---------------

OS:Red Hat Enterprise Linux ES release 3 (Taroon Update 1)

uname:Linux 2.6.27.5-117.fc10.i686 #1 SMP Tue Nov 18 12:19:59 EST 2008 i686
libc:glibc 2.9 NPTL 2.9
rlimit: STACK 10240k, CORE 0k, NPROC 1024, NOFILE 65536, AS infinity
load average:0.28 0.32 0.26

CPU:total 2 (2 cores per cpu, 1 threads per core) family 15 model 72 stepping 2, cmov, cx8, fxsr, mmx, sse, sse2, sse3, mmxext, 3dnow, 3dnowext

Memory: 4k page, physical 2072704k(84616k free), swap 1606460k(1606456k free)

vm_info: Java HotSpot(TM) Server VM (17.0-b16) for linux-x86 JRE (1.6.0_21-b06), built on Jun 22 2010 01:04:46 by "java_re" with gcc 3.2.1-7a (J2SE release)

time: Thu Nov 11 18:26:41 2010
elapsed time: 0 seconds

Analizziamo il report delcrash della JVM

Dal dump possiamo ricavare alcune informazioni che ho precedentemente evidenziato:

siginfo:si_signo=SIGSEGV: si_errno=0, si_code=1 (SEGV_MAPERR), si_addr=0x0000000d

Nelle piattaforme POSIX-compliant:

  • SIGSEGV è il segnale inviato ad un processo quando cerca di creare una referenza invalida alla memoria per esempio in caso di Segmentation Fault.
  • SEGV_MAPERR invece equivale a: Address not mapped to object, infatti sappiamo che abbiamo inserito nello stack un tipo primitivo e non un oggetto quindi il messaggio di errore è lecito!
j  Prova.main([Ljava/lang/String;)V+5

Indica che il crash è avvenuto a seguito dell’esecuzione del metodo main della classe Prova.class

=>0x0825b400 JavaThread "main" [_thread_in_vm, id=7962, stack(0xb7357000,0xb73a8000)]

Ci conferma che il crash è avvenuto nel momento in cui veniva eseguito il metodo main

Signal Handlers:
SIGSEGV: [libjvm.so+0x6a9eb0], sa_mask[0]=0x7ffbfeff, sa_flags=0x10000004

Questo indica che la JVM gestisce i Signal Handlers.
SIGSEGV è l’handler che gestisce il caso di Segmentation Fault ossia gli accessi ad un area di memoria non consentita.

In base a questi dati, l’idea che mi sono fatto è questa:

  1. L’invocazione del metodo hashCode() non è stata possibile in quanto non è stata riscontrata la presenza di un oggetto nello stack e questo ha determinato l’errore SEGV_MAPERR.
  2. A questo punto la JVM gestisce l’errore SEGV_MAPERR come SIGSEGV ossia come violazione di memoria.
  3. Il processo java rilancia l’eccezione al Sistema Operativo che lo gestisce

In base a queste considerazioni vado a controllare che il Sistema Operativo riceva l’handler di Segmentation Fault.
Considerando che utilizzo Linux, posso sniffare le system call tramite il comando strace e controllare l’errore.
Di seguito riporto solo le system-call che ci interessano:

strace -f /opt/jdk1.6.0_21/bin/java -noverify Prova
...
8747  gettimeofday({1290108273, 803687}, NULL) = 0
8747  gettimeofday({1290108273, 803786}, NULL) = 0
8747  gettimeofday({1290108273, 803904}, NULL) = 0
8747  gettimeofday({1290108273, 804003}, NULL) = 0
8747  --- SIGSEGV (Segmentation fault) @ 0 (0) ---
8747  rt_sigprocmask(SIG_UNBLOCK, [SEGV], NULL, 8 ) = 0
8747  rt_sigaction(SIGSEGV, NULL, {0xb79f1180, ~[KILL STOP RTMIN RT_1], SA_RESTART|SA_SIGINFO}, 8 ) = 0
8747  rt_sigaction(SIGBUS, NULL, {0xb79f1180, ~[KILL STOP RTMIN RT_1], SA_RESTART|SA_SIGINFO}, 8 ) = 0
8747  rt_sigaction(SIGSEGV, {0xb7b22eb0, ~[RTMIN RT_1], SA_RESTART|SA_SIGINFO}, {0xb79f1180, ~[KILL STOP RTMIN RT_1], SA_RESTART|SA_SIGINFO}, 8 ) = 0
8747  rt_sigaction(SIGBUS, {0xb7b22eb0, ~[RTMIN RT_1], SA_RESTART|SA_SIGINFO}, {0xb79f1180, ~[KILL STOP RTMIN RT_1], SA_RESTART|SA_SIGINFO}, 8 ) = 0
8747  write(1, "#\n"..., 2)             = 2
8747  write(1, "# A fatal error has been detected"..., 67) = 67
...

Ottengo le mie conferme: il sistema operativo riceve una Segmentation Fault propagato dalla JVM a seguito della mancanza dell’oggetto.

Conclusioni
Abbiamo visto come modificare un bytecode avvalendoci dei progetti jasmin e kimera che ci consentono di esplorare le opcode java e testare il Verifier.
Dall’esempio abbiamo visto come il Verifier ha preventivamente riconosciuto l’errore “Expecting to find object/array on stack” negando l’esecuzione ed evitando il crash della JVM mentre invece in caso di disattivazione del Verifier la mancanza dell’oggetto nello stack sia stato propagato come Segmentation Fault al Sistema Operativo.

Categorie:java
  1. Non c'è ancora nessun commento.
  1. 22 novembre 2010 alle 12:05 PM
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: