io_uring linked poll/timeout hrtimer UAF (CVE-2023-3389)¶
Bir io_uring poll-cancel'ını linked bir timeout'a karşı race etmek, timeout'un
hrtimercallback'inin zaten free edilmiş birio_kiocbüzerinde çalışmasına izin verir; root'a yükselen bir use-after-free.
Mechanism¶
io_uring, SQE'leri bir zincir olarak çalışsınlar diye link'lemene izin verir. Özel bir opcode, IORING_OP_LINK_TIMEOUT, kendinden önce zincirde gelen request'e bir timeout iliştirir: linked request deadline içinde tamamlanmazsa, timeout fire eder ve onu cancel eder. Timeout, request'in struct io_timeout_data'sında saklanan gerçek bir struct hrtimer ile desteklenir ve linked request REQ_F_LINK_TIMEOUT ile flag'lenir.
Note
Kernel'in uyması gereken invariant şudur: linked bir timeout'un hrtimer'ı, o request tamamlanıp free edildikten sonra asla bir io_kiocb üzerinde fire etmemeli (ya da ona karşı disarm edilmemeli). İki path head request'i tamamlayabilir — linked timeout'un kendi hrtimer callback'i (io_link_timeout_fn) ve bir poll request'i cancel etmek/kaldırmak (zincirde daha önce arm edilen IORING_OP_POLL_ADD) gibi açık bir cancellation. Bu iki path farklı locking altında çalışır, dolayısıyla interleave olabilirler. Bir poll-cancel tamamlanıp linked io_kiocb'yi tam linked timeout arm edilirken veya hrtimer'ı işlenirken free ederse, bir taraf free edilmiş memory üzerinde işlem yapar: request'e gömülü hrtimer/io_timeout_data merkezli bir use-after-free.
Warning
Free edilen object, timer'a sahip olan io_kiocb (request)'tir. Race window küçük olduğu için, gerçek exploit'ler onu genişletir ve kmalloc cache'ini groom eder ki free edilen request slot'u, dangling timer ona dokunmadan önce attacker kontrollü bir object tarafından reclaim edilsin.
Walkthrough¶
Red Hat advisory'sinden ve Querijn Voet'in "LinkedPoll" yazısından:
- Head'i bir poll request (
IORING_OP_POLL_ADD) olan ve ardından birIORING_OP_LINK_TIMEOUTSQE'si gelen bir zincir submit et, böylece headREQ_F_LINK_TIMEOUTve pending birhrtimertaşır. - Başka bir thread'den, linked timeout machinery'si (
io_link_timeout_fn/ arm-linked-timeout) headio_kiocb'ye dokunduğu tam anda poll request'i cancel/remove et (örn. async cancel path üzerinden). - İki completion path race eder: request biri tarafından free edilirken diğeri hâlâ
hrtimer/io_timeout_data'sına referans verir — hrtimer'da bir UAF. - Free edilen slab slot'unu kontrollü bir object ile reclaim et ve privilege escalation için dangling timer/request'i bir write primitive'ine pivot et.
Etkilenen sürümler ve düzeltme
Vulnerable: Linux 5.13–6.3 dahil, ve 5.10.162–5.10.184 stable hattı. Mainline commit ef7dfac51d8ed961b742218f526bd589f3900a59 ile düzeltildi (5.10'a 4716c73b188566865bdd79c3a6709696a224ac04 olarak, 5.15'e 0e388fce7aec40992eadee654193cad345d62663 olarak backport edildi). Timeout/hrtimer kodu io_uring/timeout.c'de yaşar (io_link_timeout_fn, io_timeout_data).
Detection¶
KASAN, bir async poll cancel in-flight iken io_link_timeout_fn veya hrtimer expiry path'inden geçen bir free/use stack ile bir use-after-free raporlar. Version aralığı artı bir io_uring linked-timeout workload'u pratik göstergedir.
Mitigation¶
Düzeltilmiş bir kernel'e (≥ 6.4, ya da yukarıdaki stable backport'lar) güncelle. io_uring'in gerekmediği yerlerde onu kısıtla: kernel.io_uring_disabled=2 ayarla (sysctl, 6.6+) ya da seccomp üzerinden io_uring_setup/io_uring_register'ı deny et; bu, linked-timeout path'ini tamamen kaldırır.