io_uring vulnerability (CVE-2022-29582)¶
Linkli io_uring timeout'ları arasındaki bir concurrency bug'ı,
io_link_timeout_fn()'in linkli request free edildikten sonra bir link'i yerinde bırakmasına izin verir; bu da birio_kiocbüzerinde use-after-free verir, birstruct filerefcount düşürmesine ve cross-cache üzerinden tam LPE'ye yükseltilir.
Mechanism¶
Note
io_uring, request'lerin linklenmesine izin verir: bir IORING_OP_TIMEOUT (T) bir zinciri koruyabilir ve bir IORING_OP_LINK_TIMEOUT (LT) önceki bir request'in ne kadar çalışabileceğini sınırlar. Her io_kiocb reference count'lanır (req->refs). Timeout mekanizmasının korumak zorunda olduğu invariant: LT'nin timer'ı ateşlendiğinde, LT'yi link list'inden ancak önceki request'in hâlâ canlı olduğunu atomik olarak doğruladıktan sonra çıkarır. io_link_timeout_fn()'deki zafiyetli kod bu iki adımı yanlış sırada yapıyordu:
/* vulnerable ordering (pre-5.17.3) */
if (refcount_inc_not_zero(&prev->refs))
list_del_init(&req->link_list); /* only unlinked if prev still alive */
LT'nin timer'ı ateşlendiğinde T'nin refcount'u zaten sıfıra ulaşmışsa, refcount_inc_not_zero() false döner ve list_del_init() atlanır — LT, zaten yok edilme yolunda olan bir request'e linkli kalır. Ertelenmiş completion worker'ları (io_put_req_deferred_cb) sonra sırasız çalışır ve T'nin teardown'u free edilmiş LT io_kiocb'sine dokunur. İşte bu use-after-free'dir. Pratikte race io-wq worker'ları boyunca koşar (Black Hat US-23 "Bad io_uring" sunumunda green/purple/red worker'lar olarak adlandırılır): bir worker T'nin son reference'ını düşürürken bir diğeri hâlâ LT'yi link_list üzerinden dereference eder. Her iki object da aynı cache'ten allocate edilmiş io_kiocb'ler olduğundan, race bug'ın tamamıdır; sonrasındaki her şey dar bir pencereyi deterministik bir primitive'e şekillendirmektir. 5.17.3 / 5.10.109'da e677edbcabee commit'iyle fix edildi; bu commit önce koşulsuz unlink yapar, sonra prev gitmişse onu null'lar.
Walkthrough¶
Ruia-Ruia ("Reviving Exploits Against Cred Structs") yazısını izleyerek:
-
Race'i kazan / free edilmiş object'i stabilize et. Pencere dar, bu yüzden free edilmiş LT slot'unu boş bırakmak yerine onu bir
IORING_OP_TEErequest'iyle (LT') reclaim et.do_tee(), verisi olmayan bir pipe'tan tee yaparak süresiz blok yapacak şekilde ayarlanabilir; LT'yi free edilmiş slot'a park eder ve exploit'i açık tutar. -
UAF → file refcount bug'ına çevir. T'nin yok etme path'i
io_kill_linked_timeout()çağırır; bu LT'yi canlı bir timeout olarak tanımaz, bu yüzden LT'io_fail_links()üzerinden teardown edilir.IORING_OP_TEE, input dosyasınareq->splice.file_in'de bir reference tutar; LT'yi teardown etmek o reference'ı düşürür. Pipe'ınstruct file'ı userspace hâlâ fd'yi tutarken free edilir — dangling birfile. -
filp slab'inden cross-cache ile kaç.
filecache'i type-isolated'dır, bu yüzden yazarlar slot'u yerinde yeniden kullanmaz. Slab'i tüketir, per-CPU partial list'i taşırarakunfreeze_partials()'ı zorlar, artık boş olan page'i buddy allocator'a geri verir vekmalloc-512'ye yeniden devredilmesini sağlar. -
Message segment olarak yeniden spray. Page'i
msgsnd()üzerindenmsg_msgsegobject'leriyle reclaim et, bir message body'sinin içine sahte birfileyapısı göm, sonra dangling fd'yiclose()ederek onu tekrar free et — kontrol edilebilir birmsg_msgsegUAF üretir. -
KASLR sızdır. Free edilmiş
msg_msgsegslot'larınatls_contextobject'leri (bir TCP socket'ini kTLS'e yükselterek oluşturulan) spray'le;tls_context'in içindekitcp_protpointer'ı kernel base'ini açığa çıkarır. -
Code execution. Socket'in
sk_proto->getsockopt()'unu bir stack-pivot gadget'ıyla overwrite et vegetsockopt()çağırarak bir ROP zinciri sür.
/* shape of the linked submission that arms the bug */
sqe = io_uring_get_sqe(&ring);
io_uring_prep_timeout(sqe, &ts_long, 0, 0);
sqe->flags |= IOSQE_IO_LINK; /* this is T (the parent timeout) */
sqe = io_uring_get_sqe(&ring);
io_uring_prep_link_timeout(sqe, &ts_short, 0); /* this is LT */
io_uring_submit(&ring);
/* race T's completion against LT's timer firing -> dangling io_kiocb */
Fix (commit e677edbcabee)
Warning
Bu, çok küçük bir pencereye sahip gerçek bir race — CVE metni bile bunun "belki yalnızca seyrek exploit edilebileceğini" not eder. Tüm exploit, kernel onu yeniden kullanamadan önce free edilmiş slot'u bloklayan bir IORING_OP_TEE ile park etmeye bağlıdır; bu olmadan UAF deterministik değildir. Not: bu bir Linux io_uring bug'ıdır ve bu KB'de yanında durduğu macOS/iOS maddeleriyle ilgisizdir.
Detection¶
- KASAN,
io_kiocbuse-after-free'sini ve sonrakifile/msg_msgsegreuse'unu doğrudan işaretler. - Birbirine karşı race'lenen linkli
IORING_OP_TIMEOUT+IORING_OP_LINK_TIMEOUTsubmission'ları, ardından bir pipe üzerinde bloklayan birIORING_OP_TEE, denetlemeye değer olağandışı bir opcode imzasıdır. - Cross-cache işaretleri: bir io_uring teardown'undan hemen sonra
msgsnd()taşkınları ve TLS-yükseltme (setsockopt(..., TCP_ULP, "tls")) patlamaları.
Mitigation¶
- 5.17.3'te ve 5.10.109+ / 5.15.x stable ağaçlarında patch'lendi (commit
e677edbcabee). kernel.io_uring_disabled(6.6+) veyaio_uring_setup'ı reddeden bir seccomp filtresi surface'i tamamen ortadan kaldırır.CONFIG_SLAB_FREELIST_HARDENED/_RANDOMve type-isolatedfilecache'i çıtayı yükseltir; bu exploit özellikle filp izolasyonunun etrafından bir page-level cross-cache ile dolaşır.