Bagikan melalui


Penanganan Buffer

Mungkin kesalahan paling umum dalam driver apa pun berkaitan dengan penanganan buffer di mana buffer tidak valid atau terlalu kecil. Kesalahan ini dapat memungkinkan buffer meluap atau menyebabkan crash sistem, yang merupakan kompromi keamanan untuk sistem.

Dari perspektif pengemudi, buffer datang dalam salah satu dari dua varietas:

  • Buffer halaman, yang mungkin atau mungkin tidak tinggal dalam memori.

  • Buffer non-halaman, yang harus merupakan residen dalam memori.

Tentu saja, alamat yang tidak valid tidak berpakaian halaman atau tidak berpakaian, tetapi karena sistem operasi mulai bekerja untuk menyelesaikan kesalahan halaman seperti penyebab buffer, itu akan mengisolasi alamat yang tidak valid ke salah satu rentang alamat "standar" (alamat kernel halaman, alamat kernel non-halaman, atau alamat pengguna) dan memunculkan jenis kesalahan yang sesuai. Kesalahan buffer selalu ditangani baik oleh pemeriksaan bug (PAGE_FAULT_IN_NONPAGED_AREA, misalnya) atau dengan pengecualian (STATUS_ACCESS_VIOLATION, misalnya). Dalam kasus pemeriksaan bug, sistem akan menghentikan operasi. Dalam kasus pengecualian, penangan pengecualian berbasis tumpukan akan dipanggil, dan jika tidak ada yang menangani pengecualian, maka pemeriksaan bug akan dipanggil.

Terlepas dari itu, jalur akses apa pun yang mungkin dipanggil oleh program aplikasi yang menyebabkan driver menyebabkan pemeriksaan bug adalah pelanggaran keamanan dalam driver. Ini memungkinkan aplikasi untuk menyebabkan serangan penolakan layanan ke seluruh sistem.

Salah satu masalah paling umum di area ini adalah bahwa penulis driver menganggap terlalu banyak tentang lingkungan operasi. Ini dapat mencakup:

  • Memeriksa apakah bit tinggi diatur dalam alamat. Ini tidak berfungsi pada komputer berbasis x86 di mana sistem menggunakan Four Gigabyte Tuning (4GT) dengan mengatur opsi /3GB dalam file Boot.ini. Dalam hal ini, alamat mode pengguna mengatur bit tinggi untuk gigabyte ketiga (GB) ruang alamat.

  • Menggunakan ProbeForRead dan ProbeForWrite untuk memvalidasi alamat. Meskipun ini akan memastikan bahwa alamat adalah alamat mode pengguna yang valid pada saat pemeriksaan, tidak ada yang mengharuskannya untuk tetap valid setelah operasi pemeriksaan. Dengan demikian, teknik ini memperkenalkan kondisi ras halus yang dapat menyebabkan crash berkala yang tidak dapat direproduksi. Panggilan ProbeForRead dan ProbeForWrite diperlukan karena alasan yang berbeda: untuk memvalidasi apakah alamat tersebut adalah alamat mode pengguna dan bahwa panjang buffer berada dalam rentang alamat pengguna. Jika pemeriksaan dihilangkan, pengguna dapat meneruskan alamat mode kernel yang valid, yang tidak akan tertangkap oleh blok __try dan __except (penanganan pengecualian terstruktur) dan akan membuka lubang keamanan besar. Jadi panggilan ProbeForRead dan ProbeForWrite diperlukan untuk memastikan keselarasan dan bahwa alamat mode pengguna, ditambah panjangnya, berada dalam rentang alamat pengguna. Namun, blok __try dan __except diperlukan untuk melindungi dari akses.

    Perhatikan bahwa ProbeForRead hanya memvalidasi bahwa alamat dan panjangnya termasuk dalam kemungkinan rentang alamat mode pengguna (sedikit di bawah 2 GB untuk sistem tanpa 4GT, misalnya), bukan apakah alamat memori valid. Sebaliknya, ProbeForWrite akan mencoba mengakses byte pertama di setiap halaman dengan panjang yang ditentukan untuk memverifikasi bahwa ini adalah alamat memori yang valid.

  • Mengandalkan fungsi manajer memori (MmIsAddressValid, misalnya) untuk memastikan bahwa alamat valid. Seperti halnya fungsi pemeriksaan, ini memperkenalkan kondisi balapan yang dapat menyebabkan crash yang tidak dapat direproduksi.

  • Gagal menggunakan penanganan pengecualian terstruktur. Fungsi __try dan __except dalam kompilator menggunakan dukungan tingkat sistem operasi untuk penanganan pengecualian. Pengecualian pada tingkat kernel dilemparkan kembali dengan memanggil ExRaiseStatus, atau salah satu fungsi terkait. Driver yang gagal menggunakan penanganan pengecualian terstruktur di sekitar panggilan apa pun yang dapat menimbulkan pengecualian akan menyebabkan pemeriksaan bug (biasanya KMODE_EXCEPTION_NOT_HANDLED).

    Perhatikan bahwa kesalahan untuk menggunakan penanganan pengecualian terstruktur sekeliling kode yang tidak diharapkan menimbulkan kesalahan. Ini hanya akan menutupi bug nyata yang jika tidak akan ditemukan. Menempatkan pembungkus __try dan __except di tingkat pengiriman teratas rutinitas Anda bukanlah solusi yang benar untuk masalah ini, meskipun terkadang solusi refleks yang dicoba oleh penulis driver.

  • Mengandalkan konten memori pengguna yang tetap stabil. Misalnya, driver menulis nilai ke lokasi memori mode pengguna, dan kemudian dalam rutinitas yang sama merujuk ke lokasi memori tersebut. Aplikasi berbahaya dapat secara aktif memodifikasi memori tersebut dan, akibatnya, menyebabkan driver mengalami crash.

Untuk sistem file, masalah ini sangat parah karena biasanya bergantung pada akses langsung buffer pengguna (metode transfer METHOD_NEITHER). Driver tersebut secara langsung memanipulasi buffer pengguna dan dengan demikian harus menggabungkan metode pencegahan untuk penanganan buffer untuk menghindari crash tingkat sistem operasi. I/O cepat selalu melewati pointer memori mentah, sehingga driver perlu melindungi dari masalah serupa jika I/O cepat didukung.

WDK berisi banyak contoh validasi buffer dalam kode sampel sistem file FASTFAT dan CDFS, termasuk:

  • Fungsi FatLockUserBuffer di fastfat\deviosup.c menggunakan MmProbeAndLockPages untuk mengunci halaman fisik di belakang buffer pengguna dan MmGetSystemAddressForMdlSafe di FatMapUserBuffer untuk membuat pemetaan virtual untuk halaman yang dikunci.

  • Fungsi FatGetVolumeBitmap di fastfat\fsctl.c menggunakan ProbeForRead dan ProbeForWrite untuk memvalidasi buffer pengguna dalam API defragmentasi.

  • Fungsi CdCommonRead dalam cdfs\read.c menggunakan __try dan __except sekitar kode ke buffer pengguna nol. Perhatikan bahwa kode sampel di CdCommonRead tampaknya menggunakan kata kunci coba dan kecuali. Di lingkungan WDK, kata kunci di C ini didefinisikan dalam hal ekstensi pengkompilasi __try dan __except. Siapa pun yang menggunakan kode C++ harus menggunakan jenis pengkompilasi asli untuk menangani pengecualian dengan benar, karena __try adalah kata kunci C++, tetapi bukan kata kunci C, dan akan memberikan bentuk penanganan pengecualian C++ yang tidak valid untuk driver kernel.