Keamanan dan pemverifikasi kelas

Artikel bulan ini melanjutkan diskusi tentang model keamanan Java yang dimulai pada "Under the Hood" bulan Agustus. Dalam artikel itu, saya memberikan gambaran umum tentang mekanisme keamanan yang dibangun ke dalam mesin virtual Java (JVM). Saya juga mencermati satu aspek dari mekanisme keamanan tersebut: fitur keamanan bawaan JVM. Pada "Di Balik Terpal" bulan September, saya memeriksa arsitektur pemuat kelas, aspek lain dari mekanisme keamanan bawaan JVM. Bulan ini saya akan fokus pada cabang ketiga dari strategi keamanan JVM: verifikator kelas.

Pemverifikasi file kelas

Setiap mesin virtual Java memiliki pemverifikasi file kelas, yang memastikan bahwa file kelas yang dimuat memiliki struktur internal yang tepat. Jika pemverifikasi file kelas menemukan masalah dengan file kelas, pengecualian akan dilontarkan. Karena file kelas hanyalah urutan data biner, mesin virtual tidak dapat mengetahui apakah file kelas tertentu dibuat oleh kompiler Java yang bermaksud baik atau oleh cracker teduh yang bertekad mengorbankan integritas mesin virtual. Akibatnya, semua implementasi JVM memiliki pemverifikasi file kelas yang dapat dipanggil pada kelas yang tidak tepercaya, untuk memastikan kelas aman digunakan.

Salah satu tujuan keamanan yang dibantu oleh pemverifikasi file kelas adalah ketahanan program. Jika kompiler buggy atau cracker yang cerdas menghasilkan file kelas yang berisi metode yang bytecode-nya menyertakan instruksi untuk melompat melampaui akhir metode, metode tersebut dapat, jika dipanggil, menyebabkan mesin virtual crash. Jadi, demi ketahanan, penting bahwa mesin virtual memverifikasi integritas bytecode yang diimpornya.

Meskipun desainer mesin virtual Java diizinkan untuk memutuskan kapan mesin virtual mereka akan melakukan pemeriksaan ini, banyak implementasi akan melakukan sebagian besar pemeriksaan tepat setelah kelas dimuat. Mesin virtual seperti itu menganalisis bytecode (dan memverifikasi integritasnya) satu kali, sebelum dieksekusi. Sebagai bagian dari verifikasi bytecode-nya, mesin virtual Java memastikan semua instruksi lompat - misalnya, goto(lompat selalu),ifeq(lompat jika puncak tumpukan nol), dll. - menyebabkan lompatan ke instruksi valid lain dalam aliran bytecode metode. Akibatnya, mesin virtual tidak perlu memeriksa target yang valid setiap kali menemukan instruksi jump saat menjalankan bytecode. Dalam kebanyakan kasus, memeriksa semua bytecode satu kali sebelum dieksekusi adalah cara yang lebih efisien untuk menjamin ketahanan daripada memeriksa setiap instruksi bytecode setiap kali dijalankan.

Pemverifikasi file kelas yang melakukan pemeriksaannya sedini mungkin kemungkinan besar beroperasi dalam dua fase berbeda. Selama fase pertama, yang terjadi tepat setelah kelas dimuat, pemverifikasi file kelas memeriksa struktur internal file kelas, termasuk memverifikasi integritas bytecode yang dikandungnya. Selama fase dua, yang berlangsung saat bytecode dijalankan, pemverifikasi file kelas mengonfirmasi keberadaan kelas, bidang, dan metode yang direferensikan secara simbolis.

Tahap satu: Pemeriksaan internal

Selama fase pertama, pemverifikasi file kelas memeriksa segala sesuatu yang mungkin untuk diperiksa dalam file kelas dengan hanya melihat file kelas itu sendiri (tanpa memeriksa kelas atau antarmuka lain). Fase pertama dari pemverifikasi file kelas memastikan file kelas yang diimpor dibentuk dengan benar, konsisten secara internal, mematuhi batasan bahasa pemrograman Java, dan berisi bytecode yang akan aman untuk dijalankan oleh mesin virtual Java. Jika pemverifikasi file kelas menemukan bahwa salah satu dari ini tidak benar, ini akan membuat kesalahan, dan file kelas tidak pernah digunakan oleh program.

Memeriksa format dan konsistensi internal

Selain memverifikasi integritas bytecode, pemverifikasi melakukan banyak pemeriksaan untuk format file kelas yang tepat dan konsistensi internal selama fase pertama. Sebagai contoh, setiap file kelas harus dimulai dengan empat byte yang sama, angka ajaib: 0xCAFEBABE. Tujuan dari angka ajaib adalah untuk memudahkan parser file mengenali jenis file tertentu. Jadi, hal pertama yang mungkin diperiksa oleh pemverifikasi file kelas adalah bahwa file yang diimpor memang dimulai 0xCAFEBABE.

Pemverifikasi file kelas juga memeriksa untuk memastikan file kelas tidak terpotong atau ditingkatkan dengan byte tambahan tambahan. Meskipun file kelas yang berbeda dapat memiliki panjang yang berbeda, setiap komponen individu yang terdapat di dalam file kelas menunjukkan panjangnya serta tipenya. Pemverifikasi dapat menggunakan jenis dan panjang komponen untuk menentukan panjang total yang benar untuk setiap file kelas individu. Dengan cara ini, ia dapat memverifikasi bahwa file yang diimpor memiliki panjang yang konsisten dengan konten internalnya.

Pemverifikasi juga melihat komponen individu untuk memastikannya adalah contoh yang dibentuk dengan baik dari jenis komponennya. Misalnya, deskriptor metode (tipe kembalian metode dan jumlah serta tipe parameternya) disimpan dalam file kelas sebagai string yang harus mematuhi tata bahasa bebas konteks tertentu. Salah satu pemeriksaan yang dilakukan pemverifikasi pada masing-masing komponen adalah memastikan setiap deskriptor metode adalah string tata bahasa yang sesuai dengan format yang baik.

Selain itu, pemverifikasi file kelas memeriksa bahwa kelas itu sendiri mematuhi batasan tertentu yang ditempatkan padanya oleh spesifikasi bahasa pemrograman Java. Misalnya, pemverifikasi memberlakukan aturan bahwa semua kelas, kecuali kelas Object, harus memiliki kelas super. Jadi, pemverifikasi file kelas memeriksa pada waktu proses beberapa aturan bahasa Java yang seharusnya diterapkan pada waktu kompilasi. Karena pemverifikasi tidak memiliki cara untuk mengetahui apakah file kelas dibuat oleh kompilator bebas bug yang baik hati, ia memeriksa setiap file kelas untuk memastikan aturannya diikuti.