Bagikan melalui


Penanganan Buffer

Salah satu kesalahan paling umum dalam driver apa pun berkaitan dengan penanganan buffer, di mana buffer tidak valid atau terlalu kecil. Kesalahan ini dapat memungkinkan luapan buffer atau menyebabkan crash sistem, yang dapat membahayakan keamanan sistem. Artikel ini membahas beberapa masalah umum dengan penanganan buffer dan cara menghindarinya. Ini juga mengidentifikasi kode sampel WDK yang menunjukkan teknik penanganan buffer yang tepat.

Jenis buffer dan alamat yang tidak valid

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

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

  • Buffer yang tidak dipalsukan, yang harus residen dalam memori.

Alamat memori yang tidak valid tidak di-paged atau tidak di-halaman. Saat sistem operasi bekerja untuk mengatasi kesalahan halaman yang disebabkan oleh penanganan buffer yang salah, dibutuhkan langkah-langkah berikut:

  • Ini mengisolasi alamat yang tidak valid ke dalam salah satu rentang alamat "standar" (alamat kernel halaman, alamat kernel yang tidak disebarkan, atau alamat pengguna).

  • Ini menimbulkan jenis kesalahan yang sesuai. Sistem selalu menangani kesalahan buffer baik oleh pemeriksaan bug seperti PAGE_FAULT_IN_NONPAGED_AREA, atau dengan pengecualian seperti STATUS_ACCESS_VIOLATION. Jika kesalahan adalah pemeriksaan bug, sistem akan menghentikan operasi. Dalam kasus pengecualian, sistem memanggil penangan pengecualian berbasis tumpukan. Jika tidak ada penangan pengecualian yang menangani pengecualian, sistem memanggil pemeriksaan bug.

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

Asumsi dan kesalahan umum

Salah satu masalah paling umum di area ini adalah bahwa penulis driver menganggap terlalu banyak tentang lingkungan operasi. Beberapa asumsi dan kesalahan umum meliputi:

  • Driver hanya memeriksa apakah bit tinggi diatur dalam alamat. Mengandalkan pola bit tetap untuk menentukan jenis alamat tidak berfungsi pada semua sistem atau skenario. Misalnya, pemeriksaan ini tidak berfungsi pada komputer berbasis x86 saat sistem menggunakan Four Gigabyte Tuning (4GT). Ketika 4GT digunakan, alamat mode pengguna mengatur bit tinggi untuk gigabyte ketiga ruang alamat.

  • Driver hanya menggunakan ProbeForRead dan ProbeForWrite untuk memvalidasi alamat. Panggilan ini memastikan bahwa alamat adalah alamat mode pengguna yang valid pada saat pemeriksaan. Namun, tidak ada jaminan bahwa alamat ini akan tetap valid setelah operasi pemeriksaan. Dengan demikian, teknik ini memperkenalkan kondisi balapan halus yang dapat menyebabkan crash berkala yang tidak dapat direproduksi.

    Panggilan ProbeForRead dan ProbeForWrite masih diperlukan. Jika driver menghilangkan pemeriksaan, pengguna dapat meneruskan alamat mode kernel yang valid yang __try diblokir dan __except (penanganan pengecualian terstruktur) tidak akan menangkap dan dengan demikian membuka lubang keamanan besar.

    Intinya adalah bahwa penanganan pengecualian pemeriksaan dan terstruktur diperlukan:

    • Pemeriksaan memvalidasi bahwa alamat adalah alamat mode pengguna dan bahwa panjang buffer berada dalam rentang alamat pengguna.

    • Blok __try/__except penjaga terhadap akses.

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

  • Driver yang mengandalkan fungsi manajer memori seperti MmIsAddressValid untuk memastikan bahwa alamat tersebut valid. Seperti yang dijelaskan untuk fungsi pemeriksaan, situasi ini memperkenalkan kondisi balapan yang dapat menyebabkan crash yang tidak dapat direproduksi.

  • Driver gagal menggunakan penanganan pengecualian terstruktur. Fungsi __try/except dalam kompilator menggunakan dukungan tingkat sistem operasi untuk penanganan pengecualian. Pengecualian tingkat kernel dilemparkan kembali ke sistem melalui panggilan ke ExRaiseStatus atau salah satu fungsi terkait. Driver yang gagal menggunakan penanganan pengecualian terstruktur di sekitar panggilan apa pun yang mungkin menimbulkan pengecualian akan menyebabkan pemeriksaan bug (biasanya KMODE_EXCEPTION_NOT_HANDLED).

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

  • Driver dengan asumsi bahwa konten memori pengguna akan tetap stabil. Misalnya, driver menulis nilai ke lokasi memori mode pengguna, dan kemudian dalam rutinitas yang sama yang disebutkan ke lokasi memori tersebut. Aplikasi berbahaya dapat secara aktif memodifikasi memori tersebut setelah penulisan dan, akibatnya, menyebabkan driver mengalami crash.

Untuk sistem file, masalah ini parah karena sistem file biasanya mengandalkan 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.

Kode sampel untuk penanganan buffer

WDK berisi banyak contoh validasi buffer dalam kode sampel driver 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 di cdfs\read.c menggunakan __try dan __except sekitar kode untuk buffer pengguna nol. Kode sampel di CdCommonRead tampaknya menggunakan try kata kunci dan except . Di lingkungan WDK, kata kunci ini di C didefinisikan dalam hal ekstensi __try pengkompilasi dan __except. Siapa pun yang menggunakan kode C++ harus menggunakan jenis pengkompilasi asli untuk menangani pengecualian dengan benar, seperti __try kata kunci C++, tetapi bukan kata kunci C, dan akan memberikan bentuk penanganan pengecualian C++ yang tidak valid untuk driver kernel.