Skip to content

nf_tables OOB write (CVE-2022-34918)

nft_set_elem_init() içinde NFT_DATA_VALUE ve NFT_DATA_VERDICT arasındaki bir type confusion, bir CAP_NET_ADMIN kullanıcısının attacker kontrollü veriyi fazla küçük bir slab allocation'ının ötesine yazmasına olanak veriyor.

Mechanism

The invariant that breaks

Bir element bir nftables set'ine eklendiğinde, kernel boyutu element template uzunluğundan (tmpl->len) türetilen bir slab buffer allocate eder, ardından element verisini ayrı olarak izlenen bir uzunluk (set->dlen) kullanarak içine memcpy'lar. Örtük invariant şudur: extension alanına kopyalanan byte'lar, template'in onlar için ayırdığı byte'ları asla aşmaz.

nft_data_init() kullanıcı tarafından sağlanan veriyi iki şekilden birine parse eder:

  • NFT_DATA_VALUE — ham byte'lar (bir key/value), uzunluk set->dlen'e karşı doğrulanır.
  • NFT_DATA_VERDICT — 16-byte'lık bir struct nft_verdict (bir chain jump/accept/drop), tarihsel olarak desc->len == set->dlen uzunluk kontrolünden muaf.

Bir set, bir data uzunluğu (örneğin dlen = 8) bildirilerek oluşturulur. Element template'i sonra o boyutta value-tipli bir descriptor'dan inşa edilir. Ama veri bir verdict olarak parse edilir; onun desc->len'i (sizeof(struct nft_data) = 16) daha büyüktür ve set->dlen ile hiç uzlaştırılmamıştır. Allocation value path'i için boyutlandırılırken kopya uzunluğu verdict path'ini izler — klasik bir size-confusion heap overflow. Pratikte overflow bounded'dır (RandoriSec writeup'ında ~48 byte'a kadar). Attacker farkı ve free-komşusu veriyi kontrol ettiğinden, bu güçlü bir lineer write primitive'idir.

Walkthrough

Bug, set-element add path'inde yaşar. Kavramsal olarak:

/* net/netfilter/nf_tables_api.c (pre-patch, simplified) */
err = nft_data_init(ctx, &elem.data.val, sizeof(elem.data), &desc,
                    nla[NFTA_SET_ELEM_DATA]);
/* desc.type may be NFT_DATA_VERDICT (len 16) or NFT_DATA_VALUE (len == set->dlen) */

/* template length comes from the *value* assumption ... */
tmpl  = ...; /* sized for set->dlen */
/* ... but the verdict copy uses the larger desc.len */
elem.priv = nft_set_elem_init(set, &tmpl, elem.key.val.data,
                              elem.key_end.val.data, elem.data.val.data,
                              timeout, expiration, GFP_KERNEL);

nft_set_elem_init() içinde allocation ve kopya uyuşmaz:

void *elem = kzalloc(set->ops->elemsize + tmpl->len, gfp);  /* small */
...
memcpy(nft_set_ext_data(ext), data, set->dlen);             /* writes set->dlen bytes */

Tetikleyici taslağı (hepsi netlink/NFNL_SUBSYS_NFTABLES üzerinden, CAP_NET_ADMIN gerektirir):

  1. Yeni bir user+network namespace'e gir (unshare -Urn) ki unprivileged bir process, namespace'lenmiş netfilter instance'ı üzerinde CAP_NET_ADMIN kazansın.
  2. NFT_MSG_NEWTABLE / NFT_MSG_NEWSET — küçük bir bildirilmiş data uzunluğuna sahip bir set oluştur.
  3. NFT_MSG_NEWSETELEMNFTA_SET_ELEM_DATA'sı bir verdict olarak encode edilen bir element ekle ki uzunluk kontrolü atlansın ve kopya uzunluğu allocation'ı aşmaya zorlansın.
  4. Komşu slab object'i kontrollü byte'larla overwrite edilir.
Confirming the overflow with KASAN

KASAN-instrumented bir kernel, memcpy'da out-of-bounds store'u rapor eder:

BUG: KASAN: slab-out-of-bounds in nft_set_elem_init
Write of size N at addr ffff8880........ by task poc/NNN
 nft_set_elem_init+0x...
 nf_tables_newsetelem+0x...
 nfnetlink_rcv_batch+0x...
Allocated by task NNN:
 kmalloc / nft_set_elem_init

Tam write boyutu seçilen set->dlen ve verdict uzunluğuna bağlıdır; yukarıdaki sayıları açıklayıcı kabul edin.

Footgun: this is an allocator-adjacent linear write

Primitive, element allocation'ının ardından kendi kmalloc cache'inde ne gelirse onun içine taşar. Güvenilir exploitation, kullanışlı bir victim object'in (örneğin bir length/pointer field'ı olan bir elastic object) hemen element'ten sonra oturması için heap grooming gerektirir. Bkz. elastic objects.

Detection

  • nft_set_elem_init / nf_tables_newsetelem içinde kök salan KASAN slab-out-of-bounds raporları.
  • Unprivileged process'lerin user namespace oluşturup NFNL_SUBSYS_NFTABLES batch'leri vermesini denetleme.

Mitigation

  • Fix, desc->len != set->dlen uzunluk kontrolünü tek tip uygular ve NFT_DATA_VERDICT muafiyetini kaldırır.
  • Unprivileged user namespace'leri kısıtlayın (onları açan distro'larda kernel.unprivileged_userns_clone=0; user.max_user_namespaces=0), nf_tables bug'ları için standart CAP_NET_ADMIN kapısı.
  • Mitigation'ların (CONFIG_RANDOM_KMALLOC_CACHES, vb.) overflow-sonrası aşamanın çıtasını nasıl yükselttiği için bkz. netfilter nf_tables hardened exploitation.

References