Skip to content

nf_tables limited UAF (CVE-2022-32250)

Set oluşturma sırasında stateful bir expression'ı bir set'e bind etmek, set'in ->bindings listesine bir nft_set_binding kaydeder, ama validation-başarısız cleanup expression'ı unlink etmeden free eder — geride, sonraki bir expression'ın kontrollü bir UAF write'a dönüştürdüğü bir dangling list node bırakır.

Mechanism

nftables embedded expression'larla bir set oluşturduğunda (NFT_MSG_NEWSET), nft_set_elem_expr_alloc() nft_expr_init() çağırır; bu da parent kodu NFT_EXPR_STATEFUL flag'ini yeniden kontrol etmeden önce expression'ın ->init handler'ını çalıştırır. nft_lookup/nft_dynset için bu ->init (nft_lookup_initnf_tables_bind_set) list_add_tail_rcu() aracılığıyla reference edilen set'in ->bindings doubly-linked listesine bir struct nft_set_binding ekler.

Note

İhlal edilen invariant şu: "bir liste üzerindeki her node, onu içeren object free edilmeden önce kaldırılır." Embedded expression burada gerçekten stateful olmaya izinli değilse, parent onu reddeder ve expression'ı free etmek için nft_expr_destroy() çağırır — ama cleanup path'i az önce eklediği nft_set_binding'i list_del etmeyi unutur. Set'in ->bindings listesi artık free edilmiş bellekte yaşayan bir node içerir. Free edilen expression küçüktür: nft_lookup kmalloc-48'e (binding offset 16'da), nft_dynset kmalloc-96'ya (binding offset 56'da) düşer. İkinci bir expression eklemek, prev->next = new / new->prev = prev write'ları dangling node üzerinden düşen başka bir list_add_tail gerçekleştirir, yani free-sonra-reclaim edilmiş bir object'e kontrollü bir write. Fix, binding'i destroy path'inde unlink eder.

Walkthrough

"Settlers of Netlink" zinciri (NCC Group / Theori) tek bir limited UAF write'ı dört kez tetikleyerek tam bir LPE'ye dönüştürür. Kavramsal olarak:

// 1. NEWSET with an embedded lookup/dynset expr that is NOT a valid
//    stateful expr here -> nf_tables_bind_set() adds the binding,
//    validation fails, nft_expr_destroy() frees the expr but leaves
//    the binding node on set->bindings  (dangling list node)
new_set_with_bad_dynset(set);

// 2. reclaim the freed kmalloc-96 chunk with a controlled object
spray_reclaim();                 // setxattr / FUSE / user_key_payload

// 3. add ANOTHER expr binding to the same set -> list_add_tail writes
//    next/prev pointers THROUGH the dangling node = controlled write
new_set_with_second_dynset(set);
Four UAFs: leak -> arbitrary free -> fake set -> ROP
  • UAF1 (leak): corrupting list_add write'ı reclaim edilmiş bir user_key_payload içine bir kernel set adresi yerleştirir; bunu keyctl(KEYCTL_READ) userspace'e döndürür — bir kernel heap pointer leak'i.
  • UAF2 (arbitrary free): free edilen slot'u bir cgroup_fs_context (kmalloc-96) ile reclaim et; UAF write'ı onun release_agent/path pointer'ını corrupt eder ki fd'yi close()'lamak attacker-seçimi bir nft_set'i free etsin — bir arbitrary-free primitive'i.
  • UAF3 (fake set / KASLR): o free edilen nft_set'i setxattr/FUSE aracılığıyla reclaim ederek FAKESET1'i ->udata gerçek bir set'e işaret ederek ve şişirilmiş bir ->udlen ile forge et; böylece set'i dump etmek komşu object'leri (örneğin bir tty_struct) OOB-read eder ve kernel image base'ini leak eder — KASLR yenildi.
  • UAF4 (ROP): FAKESET2'yi sahte bir op tablosuna işaret eden corrupt bir ->ops ile forge et; bir NFT_EXPR_GC expression'ı (nft_connlimit) çağırmak set->ops->gc_init()'i kontrollü register'larla çağırır → modprobe_path'i overwrite eden bir ROP zincirine pivot et, ardından bir root shell için module autoload'u tetikle.

Warning

Write primitive'i limited'tır: list_add_tail yalnızca iki pointer boyutunda değer yazmana izin verir (list next/prev) ve bu değerler tam olarak kontrol etmediğin kernel adresleridir. Tüm exploit, bu zayıf write'ı dikkatle seçilmiş reclaim object'leri aracılığıyla daha güçlü primitive'lere aklamakla ilgilidir.

Detection

KASAN, reddedilen bir NFT_MSG_NEWSET'ten sonra nf_tables_bind_set / list_add içinde bir use-after-free write işaretler; ayrıca set->bindings'den reference edilen free edilmiş bir nft_expr chunk'ına karşı bir UAF read olarak da görünebilir. Set'lere non-stateful expression eklemek başlı başına anormaldir. Stateful expression'larla set oluşturmanın syzkaller tarzı fuzzing'i bunu yeniden üretir.

Mitigation

Bir user/network namespace'de CAP_NET_ADMIN gerektirir — unprivileged path'i kaldırmak için unprivileged user namespace'leri devre dışı bırakın. Fix (commit 520778042ccca019f3ffa136dd0ca565c486cedd, "disallow non-stateful expression in sets earlier") NFT_EXPR_STATEFUL kontrolünü expr->ops->init()'in önüne taşır ve binding'i destroy path'inde unlink eder; böylece validation başarısızlığından sonra hiçbir dangling node hayatta kalmaz. nf_tables_api.c'yi 5.18.1'e kadar etkiler (5.18.2'de ve backport'larda düzeltildi). Yamalı bir kernel'e güncelleyin.

Note

Aynı limited-write primitive farklı reclaim object'leriyle de exploit edilmiştir: free edilen nft_expr chunk'ı (lookup/dynset varyantına göre kmalloc-48/96) POSIX message-queue node'ları (mqueue / msg_msg, Theori) veya user_key_payload (NCC Group) ile reclaim edilir; her ikisi de bir KASLR leak'ine ardından modprobe_path overwrite'ına götürür.

References