struct nf_conn_help contains the head of the per-master expectation list. hlist_add_head_rcu() makes the first expectation node point back to that head, but nf_ct_ext_add() can later move the extension buffer with krealloc(). This leaves the node backpointer aimed at freed memory, so unlinking the expectation writes through a stale pointer. Reserve the full u8-addressable extension space when adding the helper extension and reuse the existing buffer once the helper is present. This keeps the expectation list head stable while allowing later extensions to be added. Fixes: 857b46027d6f ("netfilter: nft_ct: add ct expectations support") Bug found and triaged by OpenAI Security Research and validated by Trail of Bits. Assisted-by: Codex:gpt-5.6-sol gpt-5.5-cyber Signed-off-by: Kyle Zeng --- Trail of Bits has a reproducer for this bug that triggers a KASAN use-after-free and can share if needed. net/netfilter/nf_conntrack_extend.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/net/netfilter/nf_conntrack_extend.c b/net/netfilter/nf_conntrack_extend.c index 0da105e1d..1421944a3 100644 --- a/net/netfilter/nf_conntrack_extend.c +++ b/net/netfilter/nf_conntrack_extend.c @@ -112,9 +112,21 @@ void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp) newlen = newoff + nf_ct_ext_type_len[id]; alloc = max(newlen, NF_CT_EXT_PREALLOC); - new = krealloc(ct->ext, alloc, gfp); - if (!new) - return NULL; + /* + * Once an expectation is linked, its list node points back to the + * hlist head in the helper extension. Reserve all available extension + * space for the helper and do not move it afterward. + */ + if (ct->ext && + __nf_ct_ext_exist(ct->ext, NF_CT_EXT_HELPER)) { + new = ct->ext; + } else { + if (id == NF_CT_EXT_HELPER) + alloc = U8_MAX; + new = krealloc(ct->ext, alloc, gfp); + if (!new) + return NULL; + } if (!ct->ext) memset(new->offset, 0, sizeof(new->offset)); -- 2.53.0