io_uring UAF (CVE-2021-41073)¶
loop_rw_iter()'da eksik bir buffer-select kontrolü, bir kernel io_buffer pointer'ını sanki userspace adresiymiş gibi ilerletir; böylece io_put_kbuf() sonradan kmalloc-32'de attacker'ın seçtiği bir offset'i free eder — use-after-free LPE'ye dönüşen kontrol edilebilir bir free.
Mechanism¶
Note
io_uring, bir request'in data buffer'ını IOSQE_BUFFER_SELECT SQE flag'i üzerinden önceden register edilmiş bir havuzdan seçmesine izin verir. Seçildiğinde kernel, req->rw.addr'da bir struct io_buffer'a (kmalloc-32'de 32-byte'lık bir object) bir pointer saklar ve REQ_F_BUFFER_SELECT'i set eder. Kodun korumakta olması gereken invariant: BUFFER_SELECT ile işaretlenmiş bir req->rw.addr bir kernel pointer'ıdır ve asla bir userspace iov adresiymiş gibi ilerletilmemeli. Bug şu: loop_rw_iter() — read_iter/write_iter olmayan dosyalar için kullanılan fallback read/write döngüsü (örneğin birçok /proc dosyası) — her transfer'den sonra flag'i kontrol etmeden req->rw.addr += nr; yapar ve kernel io_buffer pointer'ını taşınan byte sayısı kadar ilerletir.
Döngüden sonra completion, io_put_rw_kbuf() → io_put_kbuf() çalıştırır; bu da artık ilerletilmiş req->rw.addr'dan türetilen kbuf'u kfree() eder. Attacker nr'yi (transfer boyutu) kontrol ettiğinden eklenen offset'i kontrol eder, yani orijinal io_buffer'dan seçilmiş bir kayma mesafesindeki bir pointer'ı free eder:
"etkin bir şekilde
kmalloc-32cache'inde, başlangıçta allocate ettiğimiz buffer'dan kontrol edilebilir bir offset'te buffer'ları free etme yeteneği kazanırız."
Bu, canlı kernel belleğinin kontrollü bir free'sidir. Free edilmiş slot'un hâlâ başka bir yerde canlı bir reference'ı varsa, kmalloc-32'de bir use-after-free / type confusion'a dönüşür. Zafiyetli kod fs/io_uring.c'de, Linux 5.10'dan 5.14.6'ya kadar etkiliyor.
İlgili object:
struct io_buffer {
struct list_head list; /* offset 0 — first member; leaks heap pointers */
__u64 addr;
__u32 len;
__u16 bid;
}; /* exactly 32 bytes -> kmalloc-32 */
io_buffer'lar IORING_OP_PROVIDE_BUFFERS ile oluşturulur; bu aynı zamanda heap-grooming primitive'i olarak da iş görür.
Walkthrough¶
Uçtan uca, chompie exploit'ini izleyerek:
- Groom.
io_uring_prep_provide_buffers()üzerinden ~1000io_bufferspray'le;kmalloc-32'yi tüket ve öngörülebilir bitişiklik / taze slab page'leri elde et. - Yan yana getir. Free ve reclaim aynı SLUB per-CPU slab'ine vurmalı, bu yüzden ring'i
IORING_SETUP_SQPOLLile kur, worker'larıIORING_REGISTER_IOWQ_AFFile pinle ve exploit thread'lerinisched_setaffinity()ile pinle (bkz. ./io-uring-sqpoll-technique.md). - Kontrollü free'yi tetikle. Bir
loop_rw_iterdosyası (örneğin bir/procdosyası) üzerindeIOSQE_BUFFER_SELECTile bir read submit et; döngü sonrasıreq->rw.addr += nrpointer'ı kaydırır ve completion seçilmiş birkmalloc-32slot'unukfree()eder.
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, proc_fd, NULL, len, 0);
sqe->flags |= IOSQE_BUFFER_SELECT; /* req->rw.addr becomes a kernel io_buffer ptr */
sqe->buf_group = GID;
io_uring_submit(&ring); /* loop_rw_iter advances the kernel ptr, then kfrees it */
-
Reclaim (universal heap spray). Free edilmiş 32-byte'lık slot'u,
copy_from_user'ı bir FUSE read ile durdurulansetxattr()kullanarak attacker-kontrollü içerikle yeniden allocate et (vm.unprivileged_userfaultfd=0olduğundauserfaultfd'nin yerine geçer). Bloklanansetxattr, free edilmiş slot üzerinde kontrollü bir 32-byte object'i süresiz canlı tutar. -
Leak (universal heap leak).
setxattr'ı blokla, UAF free'sini tetikle, slot'a bir target object allocate et, bloku kaldır vegetxattr()üzerinden geri oku. Kullanışlı overlay hedefleri: io_tctx_node—taskalanı processtask_struct'ına işaret eden per-thread io_uring node'u.io_buffer.list(offset 0) — sprayed-object adreslerini hesaplamak için göreli heap konumunu sızdırır.-
seq_operations(/proc/cmdline'ı aç) —single_next()function pointer'ını sızdırmak KASLR'ı yener. -
Code execution.
prog'u sahte birbpf_prog'a işaret eden birsk_filteroverlay'le/forge et; sahtebpf_func/insnsbir interpreter veya ROP payload çalıştırarakcred->uid/cred->euid'i overwrite eder → root.
Spray'i boyutlandırmak için slab geometrisini okuma
Warning
Free, worker context'inde olur, bu yüzden aynı-CPU pinning olmadan reclaim spray'i (senin syscall thread'inde çalışan) farklı bir slab'e düşer ve UAF sessizce başarısız olur. SQPOLL + IORING_REGISTER_IOWQ_AFF + sched_setaffinity üçlüsü opsiyonel değil, zorunludur. Ayrıca: nr (ve dolayısıyla free edilen offset) öyle seçilmeli ki ilerletilen pointer map'li slab belleğinin içinde kalsın, yoksa kfree()'nin kendisi oops verir.
Detection¶
loop_rw_iterdosyalarını hedefleyenIOSQE_BUFFER_SELECT'li read'ler anormaldir; io_uring opcode/flag'lerini denetlemek bunu açığa çıkarır.CONFIG_SLUB_DEBUG/slub_debug=FZred-zoning, object dışındakikfreeoffset'ini yakalar; KASAN UAF read/write'ı doğrudan işaretler.- Bloklanan FUSE read'leriyle eşleşen
setxattr/getxattrpatlamaları genel bir heap-spray işaretidir.
Mitigation¶
- 5.14.7 / 5.10.x stable'da patch'lendi:
loop_rw_iter()artık buffer-select request'leri içinreq->rw.addr'ı ilerletmez. Fix commit16c8d2df7ec0eed31b7d3b61cb13206a7fb930cc("io_uring: ensure symmetry in handling iter types in loop_rw_iter()") setup/advance simetrisini geri getirir —req->rw.addr/req->rw.len'i yalnızca bvec durumu için ayarlar veiov_iter_advance()'i yalnızca non-bvec durumu için çağırır. kernel.io_uring_disabled(6.6+) veyaio_uring_setup'ın seccomp ile reddi surface'i ortadan kaldırır.CONFIG_SLAB_FREELIST_HARDENED/_RANDOMve dedicated buffer cache'lerkmalloc-32reclaim'i için çıtayı yükseltir.
References¶
- Put an io_uring on it - Exploiting the Linux Kernel (chompie)
- chompie1337/Linux_LPE_io_uring_CVE-2021-41073 (exploit source)
- CVE-2021-41073 (SentinelOne vulnerability database)
- io_uring — new code, new bugs, and a new exploit technique (STAR Labs)
- io_uring: ensure symmetry in handling iter types in loop_rw_iter() (lkml)