[PATCH] netfilter: nft_set_pipapo_avx2: bound refill rule index

From: Noah Weinberger

Date: Sun Nov 08 2026 - 17:38:27 EST


Hello,

The AVX2 refill helper lost the rule-index bound that its scalar twin
has: nft_pipapo_avx2_refill() computes i from the bitmap and
dereferences mt[i] (the caller then dereferences f->mt[ret].e) without
any 'i >= rules' check - the function does not even receive the rule
count. The scalar pipapo_refill() checks exactly this, and that guard
exists because this class of bug was once exploitable in pipapo.

The map invariant (every set bit < f->rules) currently holds across the
insert/remove/clone/adjust paths as far as static review can establish,
so there is no reachable out-of-bounds access today - this is a
defense-in-depth backstop for a set type reachable from unprivileged
user namespaces. The patch passes the rule count down and mirrors the
scalar guard (mechanical change; the 10 call sites are macro-expanded).

Affected version/commit: mainline fa4ad798f6f681031775266044ba1b0cf3c670bd (v7.4-rc1, 2026-11-07), net/netfilter/nft_set_pipapo_avx2.c:153-185 (refill), contrast nft_set_pipapo.c:372-396 (scalar twin).
The guard divergence is longstanding; the scalar guard was added after a historical pipapo CVE.

This was found during an assisted (AI-supported) static source review of
mainline fa4ad798f6f681031775266044ba1b0cf3c670bd (v7.4-rc1, 2026-11-07); every code path cited below was re-verified by the
reporter against that tree. The patch below has NOT been compiled and has
NOT been runtime tested, and no reproducer was produced - the triggering
conditions are described above instead. Please treat this as a report for
verification rather than a battle-tested fix.

Reported-by: Noah Weinberger <noahweinberger2003@xxxxxxxxx>
---
diff --git a/net/netfilter/nft_set_pipapo_avx2.c b/net/netfilter/nft_set_pipapo_avx2.c
index 21f5be6..9488c03 100644
--- a/net/netfilter/nft_set_pipapo_avx2.c
+++ b/net/netfilter/nft_set_pipapo_avx2.c
@@ -152,7 +152,8 @@ static void nft_pipapo_avx2_fill(unsigned long *data, int start, int len)
*/
static int nft_pipapo_avx2_refill(int offset, unsigned long *map,
unsigned long *dst,
- union nft_pipapo_map_bucket *mt, bool last)
+ union nft_pipapo_map_bucket *mt, int rules,
+ bool last)
{
int ret = -1;

@@ -161,6 +162,11 @@ static int nft_pipapo_avx2_refill(int offset, unsigned long *map,
while (map[(x)]) { \
int r = __builtin_ctzl(map[(x)]); \
int i = (offset + (x)) * BITS_PER_LONG + r; \
+ \
+ if (unlikely(i >= rules)) { \
+ map[(x)] = 0; \
+ return -1; \
+ } \
\
if (last) \
return i; \
@@ -242,7 +248,8 @@ static int nft_pipapo_avx2_lookup_4b_2(unsigned long *map, unsigned long *fill,
NFT_PIPAPO_AVX2_NOMATCH_GOTO(4, nomatch);
NFT_PIPAPO_AVX2_STORE(map[i_ul], 4);

- b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last);
+ b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt,
+ f->rules, last);
if (last)
ret = b;
else if (unlikely(ret == -1))
@@ -318,7 +325,8 @@ static int nft_pipapo_avx2_lookup_4b_4(unsigned long *map, unsigned long *fill,
NFT_PIPAPO_AVX2_NOMATCH_GOTO(7, nomatch);
NFT_PIPAPO_AVX2_STORE(map[i_ul], 7);

- b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last);
+ b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt,
+ f->rules, last);
if (last)
ret = b;
else if (unlikely(ret == -1))
@@ -412,7 +420,8 @@ static int nft_pipapo_avx2_lookup_4b_8(unsigned long *map, unsigned long *fill,
NFT_PIPAPO_AVX2_NOMATCH_GOTO(1, nomatch);
NFT_PIPAPO_AVX2_STORE(map[i_ul], 1);

- b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last);
+ b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt,
+ f->rules, last);
if (last)
ret = b;
else if (unlikely(ret == -1))
@@ -502,7 +511,8 @@ static int nft_pipapo_avx2_lookup_4b_12(unsigned long *map, unsigned long *fill,
NFT_PIPAPO_AVX2_NOMATCH_GOTO(8, nomatch);
NFT_PIPAPO_AVX2_STORE(map[i_ul], 8);

- b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last);
+ b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt,
+ f->rules, last);
if (last)
ret = b;
else if (unlikely(ret == -1))
@@ -637,7 +647,8 @@ static int nft_pipapo_avx2_lookup_4b_32(unsigned long *map, unsigned long *fill,
NFT_PIPAPO_AVX2_NOMATCH_GOTO(5, nomatch);
NFT_PIPAPO_AVX2_STORE(map[i_ul], 5);

- b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last);
+ b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt,
+ f->rules, last);
if (last)
ret = b;
else if (unlikely(ret == -1))
@@ -694,7 +705,8 @@ static int nft_pipapo_avx2_lookup_8b_1(unsigned long *map, unsigned long *fill,
NFT_PIPAPO_AVX2_NOMATCH_GOTO(2, nomatch);
NFT_PIPAPO_AVX2_STORE(map[i_ul], 2);

- b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last);
+ b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt,
+ f->rules, last);
if (last)
ret = b;
else if (unlikely(ret == -1))
@@ -758,7 +770,8 @@ static int nft_pipapo_avx2_lookup_8b_2(unsigned long *map, unsigned long *fill,
NFT_PIPAPO_AVX2_NOMATCH_GOTO(4, nomatch);
NFT_PIPAPO_AVX2_STORE(map[i_ul], 4);

- b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last);
+ b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt,
+ f->rules, last);
if (last)
ret = b;
else if (unlikely(ret == -1))
@@ -832,7 +845,8 @@ static int nft_pipapo_avx2_lookup_8b_4(unsigned long *map, unsigned long *fill,
NFT_PIPAPO_AVX2_NOMATCH_GOTO(0, nomatch);
NFT_PIPAPO_AVX2_STORE(map[i_ul], 0);

- b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last);
+ b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt,
+ f->rules, last);
if (last)
ret = b;
else if (unlikely(ret == -1))
@@ -917,7 +931,8 @@ static int nft_pipapo_avx2_lookup_8b_6(unsigned long *map, unsigned long *fill,
NFT_PIPAPO_AVX2_NOMATCH_GOTO(4, nomatch);
NFT_PIPAPO_AVX2_STORE(map[i_ul], 4);

- b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last);
+ b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt,
+ f->rules, last);
if (last)
ret = b;
else if (unlikely(ret == -1))
@@ -1010,7 +1025,8 @@ static int nft_pipapo_avx2_lookup_8b_16(unsigned long *map, unsigned long *fill,
NFT_PIPAPO_AVX2_NOMATCH_GOTO(6, nomatch);
NFT_PIPAPO_AVX2_STORE(map[i_ul], 6);

- b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last);
+ b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt,
+ f->rules, last);
if (last)
ret = b;
else if (unlikely(ret == -1))