Skip to content

nf_tables Flipping Pages double-free (CVE-2024-1086)

nft_verdict_init() pozitif bir drop error kabul ediyor, bu yüzden bir rule'un verdict'i NF_DROP (skb'yi free eder) olarak maskelenip NF_ACCEPT (free edilen skb'yi işlemeye devam eder) olarak geri okunabiliyor — page-level bir cross-cache attack ile root'a yükseltilen bir double-free.

Mechanism

Bir netfilter hook'u nf_hook_slow()'a bir verdict döndürür. Düşük bitler action'dır (NF_DROP, NF_ACCEPT, NF_QUEUE, …) ve NF_VERDICT_MASK ile çıkarılır; NF_DROP için üst bitler NF_DROP_GETERR() ile alınan bir errno taşır. Bir nftables rule'unun ürettiği verdict nft_verdict_init() içinde inşa edilir; bu fonksiyon userspace'in oraya ne koyabileceğini kısıtlaması gereken yerdir.

Note

Kırılan invariant şu: "tek bir verdict değerinin tek bir tutarlı anlamı vardır." nft_verdict_init() verdict code'unu doğruladı ama drop-error field'ında pozitif bir değere izin verdi. 0xffff0000 seç: 0xffff0000 & NF_VERDICT_MASK == 0 NF_DROP olarak decode edilir, bu yüzden nf_hook_slow() kfree_skb_reason(skb, ...) çalıştırır ve sk_buff'ı free eder. Ama NF_DROP_GETERR(0xffff0000) caller'ın NF_ACCEPT (devam et) gibi ele aldığı pozitif bir sayı döndürür, bu yüzden paket path'i zaten free edilmiş skb'yi kullanmaya — ve sonunda tekrar free etmeye — devam eder. Tek bir verdict hem "drop+free" hem de "accept+continue" anlamına gelir ve sk_buff'ın (ve onun ->head backing allocation'ının) bir double-free'ini verir. Fix (f342de4e2f33) nft_verdict_init() içinde pozitif drop error'larını reddeder.

Walkthrough

Tetikleyici data-only'dir: verdict register'ı özel hazırlanmış değeri tutan bir nftables rule'u insert et, ardından hook üzerinden bir paket yönlendir.

// conceptual: a rule that yields a verdict of 0xffff0000
//   NF_VERDICT_MASK -> NF_DROP   (kfree_skb path)
//   NF_DROP_GETERR  -> +1        (looks like NF_ACCEPT to the caller)
nft_add_rule_immediate_verdict(table, chain, 0xffff0000);
send_packet_through_hook();        // skb freed once, then used+freed again

skb'nin ->head'i, boyutu paket uzunluğunu izleyen bir kmalloc allocation'ıdır, bu yüzden attacker paketleri boyutlandırarak double-freed object'in hangi slab cache'ine düşeceğini seçer — kmalloc-256'dan buddy allocator'dan doğrudan gelen order-4 (64 KiB) page'lere kadar.

Flipping Pages: page-level cross-cache to Dirty Pagedirectory

"Flipping Pages" writeup'ı (Notselwyn / pwning.tech) double-free'i object granularity yerine page granularity'sinde sürer:

  1. skb'yi öyle boyutlandır ki ->head per-CPU page (PCP) allocator'a double-freed edilen bir order-0 page olsun.
  2. PCP freelist'i ve page refcount'unu drain et/hokkabazlık yap ki buddy allocator aynı physical page'i iki farklı amaç için iki kez versin.
  3. Page'i bir kez bir PTE/PMD page-table page olarak, bir kez de attacker-writable data olarak reclaim et; böylece "data" görünümüne yazmak canlı page-table entry'lerini düzenler — arbitrary physical read/write veren Dirty Pagedirectory primitive'i.
  4. Bunu modprobe_path'i bulup overwrite etmek (veya cred'i patch'lemek) için kullan; filesystem'e dokunmadan root elde et.

Page-table double-mapping, slab freelist corruption kontrollerinden kaçınır ve yeniden derlemeye gerek kalmadan v5.14–v6.6 kernel'leri arasında çalışır. Notselwyn, kernelCTF image'lerinde %99.4 başarı bildiriyor. Bkz. cross-cache attack ve Dirty Pagetable.

Warning

kfree_skb tabanlı double-free'ler timing'e duyarlıdır: skb'nin iki free arasındaki ömrünü genişletmek için IP fragmentation/reassembly kullanılır; böylece page deterministik olarak yeniden kapılabilir.

Detection

KASAN, bir netfilter NF_DROP verdict'inden sonra skb free path'inde kök salan bir double-free / use-after-free rapor eder; pozitif-errno verdict değeri (üst bitler set, düşük bitler sıfır) imzadır. Hardened-page-allocator freelist randomization page-level varyantını daha gürültülü yapar ama engellemez.

Mitigation

Bir user/network namespace'de CAP_NET_ADMIN gerektirir — unprivileged trigger'ı kaldırmak için unprivileged user namespace'leri devre dışı bırakın (user.max_user_namespaces=0). Gerçek fix, pozitif drop error'larını reddeden f342de4e2f33 commit'idir ("netfilter: nf_tables: reject QUEUE/DROP verdict parameters"). Şubat 2024 yamasının ötesinde bir kernel'e güncelleyin.

References