Skip to content

nft_tables ROP exploitation (CVE-2023-0179)

nft_payload_copy_vlan() içindeki 8-bit integer underflow, VLAN'a bağlı bir stack OOB write veriyor; önce kernel adreslerini leak ediyor, sonra nftables jumpstack'ini corrupt ederek ROP'a pivot yapıyor ve root'a ulaşıyor. (Aynı bug, nf-tables-nft-chain-nft-object-uaf slug'ında "UAF" olarak yanlış etiketlenmiş; bu not canonical'dır.)

Mechanism

Note

nftables, kuralları nft_do_chain() içinde kernel stack'inde duran küçük ve sabit boyutlu bir register file'a karşı evaluate ediyor. nft_payload_copy_vlan() bir VLAN header'ı hedef register'a yeniden kuruyor ve kaç byte kopyalanacağını bir u8 ethlen üzerinde aritmetikle hesaplıyor. skb_vlan_tag_present(skb) true olduğunda (host bir 802.1Q VLAN içindeyse), boyut hesabı unsigned 8-bit ethlen'i underflow ediyor; yani küçük olması gereken bir değer ~250–254'e wrap oluyor. Bu durumda copy, seçilen register'ı çok aşarak doğrudan komşu stack belleğine yazıyor — chain evaluation'ı resume etmek için kullanılan jumpstack de buna dahil.

Kırılan invariant şu: kopyalanan miktar hedef register'ın sınırları içinde kalmalı, ama unsigned underflow "çok büyük" bir length'in bounds mantığından fark edilmeden geçmesine yol açıyor. Zafiyetli şekil kabaca şöyle:

/* net/netfilter/nft_payload.c, simplified */
u8 ethlen = len;
...
if (offset + len > VLAN_ETH_HLEN + vlan_hlen)
    ethlen -= offset + len - VLAN_ETH_HLEN + vlan_hlen;  /* underflows u8 */

offset = 19, len = 4 ile çıkarma işlemi underflow oluyor ve ethlen 251 oluyor; böylece seçilen register'dan itibaren ~251 byte'a kadar yazılıyor.

Walkthrough

Saldırının CAP_NET_ADMIN'e ihtiyacı var; unprivileged bir kullanıcı bunu yeni bir user namespace'e girerek elde ediyor. Ayrıca skb'nin bir VLAN tag taşıması gerekiyor, bu yüzden PoC bir VLAN interface kuruyor.

  1. Namespace'lere gir ve CAP_NET_ADMIN kazan:
unshare(CLONE_NEWUSER | CLONE_NEWNET);   /* map self to uid 0 inside */
/* configure a VLAN device so skb_vlan_tag_present() is true */
  1. Leak. payload destination olarak NFT_REG32_00'ı seç ki oversized copy, stack içeriğini register file'ın içine taşırsın; sonra bu register'ları nft_dynset expression'larıyla bir nft_set'e depolayarak geri oku. Set'i dump etmek (nft list ruleset) stack ve heap/text pointer'larını ortaya çıkarıyor ve KASLR'ı kırıyor.

  2. Corrupt. destination olarak NFT_REG32_15 ile yeniden tetikle. Artık ~251-byte'lık write, register'ları aşıp nft_do_chain()'in NFT_JUMP verdict'leri boyunca sakladığı jumpstack girdilerine (chain, rule, last_rule pointer'ları) ulaşıyor. Bir dizi jump verdict ile jumpstack'i önceden artırmak, corrupt edilebilir slot'u konumlandırıyor.

  3. Pivot. chain evaluation resume olduğunda, forge edilmiş rule pointer dereference ediliyor ve "expression"ları evaluate ediliyor. Onu (leak edilmiş bir adrese yerleştirilen) saldırgan verisine yönlendirmek execution flow kontrolünü veriyor; ardından bir stack pivot, commit_creds(prepare_kernel_cred(0))'ı (ya da eşdeğerini) çağıran bir ROP chain çalıştırıyor ve userspace'e root olarak dönüyor.

Conceptual leak readout
$ nft list ruleset
table ip exploit {
    set leak {
        elements = { 0xffffffff8... , 0xffff8881... }   # text + heap ptrs
    }
}

Warning

The exact byte counts and register layout are kernel-version-specific; the PoC was validated on Linux 6.1.6. The bug is reachable from commit f6ae9f1 through 696e1a48b1a1 (mainline up to 6.2-rc), but only when CONFIG_VLAN_8021Q is built and a VLAN tag is present. Treat the offsets above as illustrative, not copy-paste exploit constants.

Detection

Unprivileged creation of nftables rulesets that use nft_payload with VLAN extraction on a freshly created user+net namespace is suspicious. Auditd on unshare(CLONE_NEWUSER) plus unexpected VLAN interface creation by non-root narrows it.

Mitigation

  • Update past the fix: the patch corrects the operator so the size cannot underflow (... - VLAN_ETH_HLEN - vlan_hlen instead of + vlan_hlen). Fixed by commit 696e1a48b1a1b01edad542a1ef293665864a4dd0 ("netfilter: nft_payload: incorrect arithmetics when fetching VLAN header bits"); reachable in 6.2-rc and backported.
  • Disable unprivileged user namespaces: sysctl -w kernel.unprivileged_userns_clone=0 removes the CAP_NET_ADMIN acquisition path.

References