io_uring reference counting bug (CVE-2021-20226)¶
Erken bir io_uring, caller'ın
files_struct'ını bir worker thread'le reference almadan paylaşır; böylecefdget()'in single-threaded fast path'i per-filerefcount'u atlar ve eşzamanlı birIORING_OP_CLOSEhâlâ kullanımda olan birstruct file'ı free eder — LPE'ye götüren bir refcount-underflow use-after-free.
Mechanism¶
io_uring işi bir kernel worker'ına devrettiğinde, worker submitter'ın open-file table'ına ihtiyaç duyar. io_grab_files() bunu request üzerine kaydeder. Bug, table'ı fazladan kullanıcıyı hesaba katmadan paylaşmasıdır.
Note
Burada iki reference-count invariant'ı birleşir. (1) io_grab_files(), files_struct refcount'unu artırmadan req->work.files = current->files; yapar; böylece artık table'ı bir worker tutsa da files->count yapay olarak düşük kalır. (2) fdget()'in bir optimizasyonu var: atomic_read(&files->count) == 1 olduğunda struct file'ı per-file refcount'u artırmadan döndürür ("fd table paylaşılmıyorsa refcnt artırma yok"). Worker table'ı paylaşırken files->count hâlâ 1 okuduğundan, fdget() table'ın paylaşılmadığını sanır ve koruyucu reference olmadan bir file pointer'ı verir. Sahip task sonra worker hâlâ onu kullanırken o file'ı (örneğin IORING_OP_CLOSE ile) free edebilir — struct file üzerinde use-after-free üreten fiili bir refcount underflow.
Warning
Free edilen object bir struct file'dır (filp cache). Dangling worker erişiminden önce slot'u reclaim etmek — worker'ın privileged bir files_struct devralması için setuid bir binary'nin execve()'si, root-sahipli fd'leri okumak için IORING_OP_FILES_UPDATE ile birleştirilerek — UAF'ı arbitrary read/write'a ve root'a çevirir.
Walkthrough¶
GMO Flatt Security (Ryota Shiga) ve ZDI analizlerinden (ZDI-21-001):
- io_uring işi submit et ki bir worker
current->files'ıio_grab_files()üzerinden ek reference olmadan tutsun. files_struct.count == 1tut kifdget()worker'ın erişimlerinde per-filerefcount'u atlasın.- Sahip task'a
IORING_OP_CLOSEçalıştırt ki worker hâlâ korumasız path üzerinden ona reference verirkenstruct filefree edilsin. - Worker'ı operation ortasında durdurup race penceresini genişletmek için
userfaultfdkullan; free edilmişfilpslot'unu kontrollü bir object'le reclaim et ve LPE için dangling reference üzerinde işlem yap.
Etkilenen sürümler ve fix
Zafiyetli: Linux 5.6'dan 5.7'ye fs/io_uring.c (open/close-tarzı file op'ları io_uring'e eklendikten sonra). Commit 0f2122045b946241a9e549c2a76cea54fa58a7ff ile fix edildi; bu commit files_struct'ı worker'lara verirken düzgün bir reference alır, böylece files->count > 1 olur ve fdget() artık refcounting'i atlamaz.
Detection¶
KASAN, IORING_OP_CLOSE/IORING_OP_FILES_UPDATE civarında bir io_uring worker'ından ulaşılan bir struct file üzerinde use-after-free raporlar. 5.6–5.7 sürüm aralığı güvenilir göstergedir.
Mitigation¶
Fix'li bir kernel'a güncelle (fix'leyen commit / 5.7+ stable backport'ları). Sonraki kernel'lar io_uring'in file-table işleyişini yeniden tasarladı; unprivileged io_uring'i devre dışı bırakmak path'i ortadan kaldırır.