flawopen.com/Teardowns/cve-2024-0582-linux-io-uring-privilege-escalation
CVE-2024-0582: Use-After-Free Ring Buffer Ter-mmap di io_uring Linux
CVE-2024-0582 (CVSS 7.8, tinggi): ring buffer yang didaftarkan dengan IORING_REGISTER_PBUF_RING dan IOU_PBUF_RING_MMAP dibebaskan saat pembongkaran padahal ruang pengguna masih memetakannya dengan mmap(), menyisakan satu halaman yang bisa dibaca dan ditulis penyerang setelah kernel memakainya kembali, sehingga terjadi peningkatan hak istimewa lokal.
Sebuah perpustakaan membiarkan Anda mendaftarkan ruang baca khusus dan memberi Anda kunci untuk keluar-masuk. Saat Anda membatalkan pendaftaran ruang itu, pustakawan langsung mengembalikannya ke daftar tersedia, tetapi tidak pernah menarik kunci Anda. Perpustakaan lalu menyewakan ruang yang sama kepada orang lain, dan Anda masih bisa masuk dengan kunci lama dan menulis ulang apa pun yang ditinggalkannya di meja. Perbaikannya adalah menjaga setiap ruang semacam itu tetap dipesan dan hanya melepasnya sekali, saat tutup, setelah semua kunci dikembalikan.
Konsep Kunci & Istilah
io_uring- Antarmuka I/O asinkron Linux. Aplikasi dan kernel berbagi ring pengiriman dan penyelesaian, sehingga tidak perlu satu syscall per operasi.
Ring buffer yang disediakan (IORING_REGISTER_PBUF_RING)- Ring buffer yang didaftarkan aplikasi agar kernel memilih satu per operasi. Dengan
IOU_PBUF_RING_MMAP, kernel mengalokasikan ring dan aplikasi mem-mmap()-nya alih-alih menyediakan memorinya sendiri. Use-after-free (UAF)- Memakai memori setelah dibebaskan. Begitu kernel mengalokasikan ulang memori itu untuk objek lain, referensi usang dapat membaca atau merusaknya.
remap_pfn_range / folio_put- Ring dipetakan ke ruang pengguna dengan
remap_pfn_range(), jadi pembukuan munmap biasa tidak membebaskannya. Memanggilfolio_put()saat pembongkaran membebaskan halaman padahal pemetaan masih aktif.
Pemetaan kerangka kerja
- Referensi CWE untuk kelemahan yang dijelaskan dalam panduan ini: CWE-416
Analisis Akar Masalah (Root Cause)
Untuk ring buffer yang dibuat dengan IOU_PBUF_RING_MMAP, io_alloc_pbuf_ring() mengalokasikan halaman dan ruang pengguna memetakannya dengan mmap() lewat remap_pfn_range(). Karena jalur itu melewati pembukuan munmap/release biasa, __io_remove_buffers() yang memanggil folio_put() membebaskan halaman padahal pemetaan pengguna masih mungkin ada — sebuah use-after-free (CWE-416): ruang pengguna tetap memegang pemetaan yang sah ke halaman yang sudah dikembalikan kernel ke alokator dan bisa dialokasikan ulang. Jann Horn melaporkannya. Commit c392cbecd8ec mencatat setiap ring yang dialokasikan mmap ke io_buf_list baru dan hanya membebaskannya di io_kbuf_mmap_list_free() saat ->release(), setelah pemetaan hilang.
Alur Serangan Langkah demi Langkah
Mendaftarkan ring berbasis mmap
Proses tanpa hak istimewa memanggil io_uring_register() dengan IORING_REGISTER_PBUF_RING dan flag IOU_PBUF_RING_MMAP, sehingga kernel mengalokasikan ring buffer.
Memetakannya ke ruang pengguna
Proses mem-mmap() ring itu. Kernel memetakan halaman dengan remap_pfn_range(), dan proses mempertahankan pemetaan.
Membebaskan ring saat masih dipetakan
Membatalkan pendaftaran buffer (atau membongkar ring) mencapai __io_remove_buffers(), yang folio_put()-nya membebaskan halaman meski pemetaan pengguna masih aktif.
Menulis lewat pemetaan usang
Kernel mengalokasikan ulang halaman yang dibebaskan untuk objek lain; proses yang masih memegang pemetaan membaca dan menimpanya untuk merusak keadaan kernel dan naik ke root.
Kode Sumber: Rentan vs Aman
/* io_uring/kbuf.c sebelum perbaikan (Linux 6.6.4) */
static int io_alloc_pbuf_ring(struct io_uring_buf_reg *reg,
struct io_buffer_list *bl)
{
gfp_t gfp = GFP_KERNEL_ACCOUNT | __GFP_ZERO | __GFP_NOWARN | __GFP_COMP;
size_t ring_size;
void *ptr;
ring_size = reg->ring_entries * sizeof(struct io_uring_buf_ring);
ptr = (void *) __get_free_pages(gfp, get_order(ring_size));
if (!ptr)
return -ENOMEM;
bl->buf_ring = ptr;
bl->is_mapped = 1;
bl->is_mmap = 1;
return 0;
}
static int __io_remove_buffers(struct io_ring_ctx *ctx,
struct io_buffer_list *bl, unsigned nbufs)
{
if (bl->is_mapped) {
i = bl->buf_ring->tail - bl->head;
if (bl->is_mmap) {
/* BUG: ring diberikan ke ruang pengguna melalui mmap() dan
* remap_pfn_range(). Di sini folio_put() melepas halaman saat
* pemetaan mungkin masih ada, sehingga ruang pengguna terus menulis ke
* halaman kernel yang telah dilepas lalu dialokasikan kembali. */
folio_put(virt_to_folio(bl->buf_ring));
bl->buf_ring = NULL;
bl->is_mmap = 0;
}
}
/* ... */
}
/* io_uring/kbuf.c setelah commit c392cbecd8ec (Linux 6.6.5 / 6.7) */
struct io_buf_free {
struct hlist_node list;
void *mem;
};
static int io_alloc_pbuf_ring(struct io_ring_ctx *ctx,
struct io_uring_buf_reg *reg,
struct io_buffer_list *bl)
{
struct io_buf_free *ibf;
size_t ring_size;
void *ptr;
ring_size = reg->ring_entries * sizeof(struct io_uring_buf_ring);
ptr = io_mem_alloc(ring_size);
if (!ptr)
return -ENOMEM;
/* FIX: catat setiap ring yang dapat di-mmap agar halamannya hanya dilepas saat
* ->release(), setelah pemetaan hilang, bukan selama teardown. */
ibf = kmalloc(sizeof(*ibf), GFP_KERNEL_ACCOUNT);
if (!ibf) {
io_mem_free(ptr);
return -ENOMEM;
}
ibf->mem = ptr;
hlist_add_head(&ibf->list, &ctx->io_buf_list);
bl->buf_ring = ptr;
bl->is_mapped = 1;
bl->is_mmap = 1;
return 0;
}
static int __io_remove_buffers(struct io_ring_ctx *ctx,
struct io_buffer_list *bl, unsigned nbufs)
{
if (bl->is_mapped) {
i = bl->buf_ring->tail - bl->head;
if (bl->is_mmap) {
/* io_kbuf_mmap_list_free() melepas halaman pada ->release(),
* jadi tidak ada halaman yang dilepas di sini selama masih mungkin dipetakan. */
bl->buf_ring = NULL;
bl->is_mmap = 0;
}
}
/* ... */
}
/* Dipanggil saat atau setelah ->release(): ring yang di-mmap kini aman untuk dilepas. */
void io_kbuf_mmap_list_free(struct io_ring_ctx *ctx)
{
struct io_buf_free *ibf;
struct hlist_node *tmp;
hlist_for_each_entry_safe(ibf, tmp, &ctx->io_buf_list, list) {
hlist_del(&ibf->list);
io_mem_free(ibf->mem);
kfree(ibf);
}
}
Daftar Periksa Penguatan Sistem Rekayasa
- ✓Jalankan kernel yang memuat commit c392cbecd8ec (Linux 6.6.5, 6.7 atau lebih baru, atau kernel distribusi yang changelog-nya mencantumkan
CVE-2024-0582). - ✓Di tempat beban kerja tidak membutuhkan io_uring, batasi: setel
kernel.io_uring_disabled=2, atau kendalikan per beban kerja dengan mekanisme seccomp/SELinux distribusi Anda. - ✓Saat memori diserahkan ke ruang pengguna lewat
mmap()/remap_pfn_range(), ikat masa hidupnya ke->release(), bukan ke jalur pembongkaran yang lebih awal, agar dibebaskan sekali saja setelah pemetaan hilang. - ✓Bangun dan fuzz kernel dengan KASAN dan
slub_debugagar pembebasan-saat-dipetakan tertangkap sebagai use-after-free dalam pengujian. - ✓Untuk beban kerja lokal yang tidak tepercaya, utamakan kernel yang dibangun tanpa
CONFIG_IO_URING, atau blokir syscallio_uring_setup/io_uring_registerdengan seccomp.