found a stack out-of-bounds read in the Linux kernel's nftables pipapo set backend (CVE-2026-43453, CVSS 7.1). I found it by looking for a specific pattern that I think is underhunted, so I want to talk about the methodology as much as the bug.
the pattern: function calls where one argument is a boundary-dependent expression and another argument is a flag that makes the callee skip using it. in C, this is a trap. the callee's early return makes every reviewer think the dangerous argument is inert. it is not. C evaluates all arguments at the call site before the function is invoked. the callee's control flow has no jurisdiction over argument evaluation. so you get these call sites that look safe, that have been reviewed and re-reviewed and look safe every time, because the question everyone asks is "is this value used?" and the answer is no. the question that matters is "is this value evaluated?" and nobody asks it because in most languages it's the same question.
so I started grepping function calls where an argument indexes an array, and a separate argument is a boolean that triggers an early return in the callee. the kind of code where someone wrote a guard clause and everyone downstream trusted it to cover the arguments too. it doesn't. it can't. the arguments are already computed.
pipapo_drop() in nft_set_pipapo.c:
pipapo_unmap(f->mt, f->rules, rulemap[i].to, rulemap[i].n, rulemap[i 1].n, i == m->field_count - 1)
on the last iteration, i == field_count - 1. rulemap[i 1].n reads past the end of a stack-allocated array of 16 entries. pipapo_unmap() checks is_last, returns immediately, never touches the value. the value is already read. the OOB is in the caller's scope. five years of this code in production and every review pass concluded "the function doesn't use it" which is true and also completely beside the point.
the reason I think this pattern is underhunted: static analyzers flag unused variables and unchecked return values but I haven't seen one that asks "is this argument expression legal in the caller's scope given that the callee might not use it?" the safety of the expression depends on the callee's behavior, but the evaluation of the expression doesn't. that gap is where bugs live for years. maybe decades. the callee being careful is what makes the bug invisible. the better the function handles its arguments, the longer the OOB at the call site survives review. that's perverse. the code's own correctness is camouflaging the bug.
when field_count is 16 (NFT_PIPAPO_MAX_FIELDS, the max), rulemap[16].n is real stack OOB. you're reading whatever the kernel left on the stack before your frame. smaller field counts get you uninitialized entries in your own array instead, which is a different flavor of wrong but still wrong. and this isn't some exotic race you trigger with three threads and a prayer. it's the normal path. every element expiration, every deletion. the kernel's own GC walks into it on a timer.
KASAN on 7.0.0-rc2 aarch64 confirmed it: Read of size 4 at addr ffff8000810e71a4. one stack object, [32, 160) 'rulemap', buggy address at offset 164. array is 128 bytes. read is 4 bytes past the end. rulemap[16].n. worked the offset math on paper beforehand.
PoC: pipapo set with NFT_SET_INTERVAL | NFT_SET_CONCAT | NFT_SET_TIMEOUT, 16 concatenated 4-byte fields. insert element, 1-second timeout. wait. insert another to trigger nft_pipapo_commit() → pipapo_gc() → pipapo_drop() → OOB. no heap shaping. no race. the kernel GC walks into it on a schedule.
reported to security@kernel.org. Willy Tarreau forwarded to netfilter maintainers. Florian Westphal reviewed, confirmed, asked for a readability tweak. the fix:
last ? 0 : rulemap[i 1].n, last
I think there are more of these in the kernel. any function that takes a flag argument and an expression argument where the flag makes the expression unnecessary. every one of those call sites is a candidate for an OOB or an uninitialized read that no reviewer will catch because the callee's guard clause is too convincing. the code review feedback loop is broken for this pattern. the only reliable way to find them is to stop reading the callee entirely and ask whether every argument is legal to evaluate in the caller's scope, regardless of what the function plans to do with it.
patched in stable 5.10–6.19.