From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail.netfilter.org (mail.netfilter.org [217.70.190.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7BF35449987; Mon, 10 Aug 2026 19:40:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.70.190.124 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786390851; cv=none; b=TUy0JCcgXFfL+M4XAjmOsC7wHUfyMxHATvX3pVt6AfX+DmFrxpt7W1ZdyVl1emNEIvfVgEMpMTiSsQF1Veg8igylemkF77+fgN8xP7+qHQUQZHZsspBzwsVEpQE5DgRnzLcI5ePCvsEXG7Ph1sIjnsRXrqt5urVUfeDgEf+x5jo= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786390851; c=relaxed/simple; bh=TDnR2fgbp5ym0UrJZ9QWeto9bH/hf4PSLOM0URVcG3A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WdBEK02ve//rElqX/lZh+k+Wp3lLovd5vWJ18kPtQO6NZ/C2QGDe58uGhBpLsqssgKgUrYQR0ve0SePtTcbfvKT4ZA0TetheeuTP3OjL1wRXGjU176kx4Xto7i31pFFvKqdRplHUMl2PeLAT3pDwjwc9L3YCUyyFECDK1saaiL4= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=netfilter.org; spf=pass smtp.mailfrom=netfilter.org; dkim=pass (2048-bit key) header.d=netfilter.org header.i=@netfilter.org header.b=PUN7SBWs; arc=none smtp.client-ip=217.70.190.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=netfilter.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=netfilter.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=netfilter.org header.i=@netfilter.org header.b="PUN7SBWs" Received: from localhost.localdomain (mail-agni [217.70.190.124]) by mail.netfilter.org (Postfix) with ESMTPSA id 80B4D601C2; Mon, 10 Aug 2026 21:40:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=netfilter.org; s=2025; t=1786390837; bh=pJiP4MNWqB4lo/kbEM+7331NOjn2NeurT5tMuuaIcvY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PUN7SBWsoCnd1vkLmrqpkCyJeQEGGDjaWm6+zwxiaiVrJGoMwA4l62r62ngUTXZ3m LMQM+VYWTJf06ynbEf0cv9q2SzDLS89D754bPxFw7f+LndAuRi2MhnoJjxHoVUHjU8 DX7W1lbJ62xG1Tef5cFtbtAFSH3gT0fSAINxtXNJYvkIwxT6uUVongKnow2U4fq5AL h3Fi0KpP5nD0bdOobHXaU0SzcoQ5SdY+OjUbYD29zf796i8igtP2dCF47Qfl3KtrbV BMvI/9+utRcHqCT/MH6hR25hpK5GkvSipBRQZ+Y/e3gGEr7MTw1q3GA2Txadg5gz6x DmNPPJwRoUNZg== From: Pablo Neira Ayuso To: netfilter-devel@vger.kernel.org Cc: davem@davemloft.net, netdev@vger.kernel.org, kuba@kernel.org, pabeni@redhat.com, edumazet@google.com, horms@kernel.org, fw@strlen.de, ja@ssi.bg Subject: [PATCH net-next 09/12] netfilter: nft_ct: move custom expectation support to helper Date: Mon, 10 Aug 2026 21:40:12 +0200 Message-ID: <20260810194015.932627-10-pablo@netfilter.org> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260810194015.932627-1-pablo@netfilter.org> References: <20260810194015.932627-1-pablo@netfilter.org> Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Originally, the ct expectation support called nf_ct_helper_ext_add() for confirmed conntracks, which is invalid, triggering a splat. This was fixed by commit 1710eb913bdc ("netfilter: nft_ct: skip expectations for confirmed conntrack") which restricted it to unconfirmed conntracks. However, early insertion of expectations into the expectations list when the conntrack is unconfirmed leads to stale entries pointing to the wrong hlist_head through .pprev due to ct extension reallocation. Commit 7c9664351980 ("netfilter: move nat hlist_head to nf_conn") moved the nat hlist_head to nf_conn for this reason: 1. ... 2. When reallocation of extension area occurs we need to fixup the bysource hash head via hlist_replace_rcu. I'd rather not increase the size of the struct nf_conn for this feature has very limited scope: only one expectation can be created at a time given expect_clash() will make nf_ct_expect_related() reports EBUSY. For this reason, relax nf_ct_expect_related() not to drop packets in case expectation creation fails, therefore, expectation creation becomes best effort. To address this issue, add an internal ct helper and attach it to the conntrack entry to streamline the custom ct expectation support with existing ct helpers. Expose a new nf_conntrack_helper_release() function to release the internal helper that is allocated and attached to the conntrack entry to create the custom expectations. The nft_ct module removal always waits for rcu grace period, then the NULL helper callback is observed after this. This patch also restricts the creation of expectations to different helpers other than this custom helper that is created for this type of expectations. Fixes: 857b46027d6f ("netfilter: nft_ct: add ct expectations support") Reported-by: Jaeyeong Lee Link: https://patch.msgid.link/20260715144755.00ea7dfcd9f@proton.me Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_conntrack_helper.h | 1 + net/netfilter/nf_conntrack_helper.c | 14 +- net/netfilter/nft_ct.c | 167 +++++++++++++++----- 3 files changed, 135 insertions(+), 47 deletions(-) diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h index bc5427d239f4..335b8c43694f 100644 --- a/include/net/netfilter/nf_conntrack_helper.h +++ b/include/net/netfilter/nf_conntrack_helper.h @@ -106,6 +106,7 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, int nf_conntrack_helper_register(struct nf_conntrack_helper *, struct nf_conntrack_helper **); int __nf_conntrack_helper_register(struct nf_conntrack_helper *); void nf_conntrack_helper_unregister(struct nf_conntrack_helper *); +void nf_conntrack_helper_release(struct nf_conntrack_helper *); int nf_conntrack_helpers_register(struct nf_conntrack_helper *, unsigned int, struct nf_conntrack_helper **); diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c index 506c58034761..c30ae3f203be 100644 --- a/net/netfilter/nf_conntrack_helper.c +++ b/net/netfilter/nf_conntrack_helper.c @@ -448,6 +448,15 @@ static bool expect_iter_me(struct nf_conntrack_expect *exp, void *data) return this == me; } +void nf_conntrack_helper_release(struct nf_conntrack_helper *me) +{ + nf_ct_expect_iterate_destroy(expect_iter_me, me); + + if (refcount_dec_and_test(&me->ct_refcnt)) + kfree_rcu(me, rcu); +} +EXPORT_SYMBOL_GPL(nf_conntrack_helper_release); + void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) { mutex_lock(&nf_ct_helper_mutex); @@ -463,10 +472,7 @@ void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) */ synchronize_rcu(); - nf_ct_expect_iterate_destroy(expect_iter_me, me); - - if (refcount_dec_and_test(&me->ct_refcnt)) - kfree_rcu(me, rcu); + nf_conntrack_helper_release(me); } EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister); diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c index 358b9287e12e..9dbf127df9c8 100644 --- a/net/netfilter/nft_ct.c +++ b/net/netfilter/nft_ct.c @@ -1213,6 +1213,8 @@ struct nft_ct_expect_obj { u8 l4proto; u8 size; u32 timeout; + + struct nf_conntrack_helper *helper; }; static int nft_ct_expect_timeout_get(const struct nlattr *attr, u32 *val) @@ -1226,6 +1228,93 @@ static int nft_ct_expect_timeout_get(const struct nlattr *attr, u32 *val) return 0; } +#if IS_ENABLED(CONFIG_NF_NAT) +static void nft_ct_nat_follow_master(struct nf_conn *ct, struct nf_conntrack_expect *this) +{ + const struct nf_ct_helper_expectfn *expfn; + + expfn = nf_ct_helper_expectfn_find_by_name("nat-follow-master"); + if (expfn) + expfn->expectfn(ct, this); +} +#endif + +struct nft_ct_expect_data { + struct nft_ct_expect_obj obj; + enum ip_conntrack_dir dir; +}; + +static int ct_expect_help(struct sk_buff *skb, unsigned int protoff, + struct nf_conn *ct, enum ip_conntrack_info ctinfo) +{ + enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); + struct nft_ct_expect_data *expect_data; + struct nf_conntrack_expect *exp; + int ret = NF_ACCEPT; + u16 l3num; + + if (nf_ct_is_confirmed(ct)) + return NF_ACCEPT; + + expect_data = nfct_help_data(ct); + if (!expect_data) + return NF_ACCEPT; + + if (expect_data->dir != dir) + return NF_ACCEPT; + + exp = nf_ct_expect_alloc(ct); + if (!exp) + return NF_DROP; + + if (expect_data->obj.l3num == NFPROTO_INET) + l3num = nf_ct_l3num(ct); + else + l3num = expect_data->obj.l3num; + + nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, l3num, + &ct->tuplehash[!dir].tuple.src.u3, + &ct->tuplehash[!dir].tuple.dst.u3, + expect_data->obj.l4proto, NULL, &expect_data->obj.dport); + exp->timeout += expect_data->obj.timeout; + +#if IS_ENABLED(CONFIG_NF_NAT) + if (ct->status & IPS_NAT_MASK) { + exp->saved_proto.tcp.port = expect_data->obj.dport; + exp->dir = !dir; + exp->expectfn = nft_ct_nat_follow_master; + } +#endif + if (nf_ct_expect_related(exp, 0) != 0) + ret = NF_ACCEPT; + + nf_ct_expect_put(exp); + + return ret; +} + +static int nft_ct_expect_helper_alloc(struct nft_ct_expect_obj *priv) +{ + struct nf_conntrack_helper *ct_expect_helper; + + ct_expect_helper = kzalloc_obj(struct nf_conntrack_helper, + GFP_KERNEL_ACCOUNT); + if (!ct_expect_helper) + return -ENOMEM; + + snprintf(ct_expect_helper->name, sizeof(ct_expect_helper->name), "%s", + "nft_ct_expect"); + ct_expect_helper->me = THIS_MODULE; + ct_expect_helper->expect_policy[NF_CT_EXPECT_CLASS_DEFAULT].max_expected = priv->size; + rcu_assign_pointer(ct_expect_helper->help, ct_expect_help); + refcount_set(&ct_expect_helper->ct_refcnt, 1); + + /* No need to register this helper, this is internal. */ + priv->helper = ct_expect_helper; + + return 0; +} + static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, const struct nlattr * const tb[], struct nft_object *obj) @@ -1233,6 +1322,8 @@ static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, struct nft_ct_expect_obj *priv = nft_obj_data(obj); int err; + NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nft_ct_expect_data)); + if (!tb[NFTA_CT_EXPECT_L4PROTO] || !tb[NFTA_CT_EXPECT_DPORT] || !tb[NFTA_CT_EXPECT_TIMEOUT] || @@ -1272,13 +1363,31 @@ static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, priv->dport = nla_get_be16(tb[NFTA_CT_EXPECT_DPORT]); priv->size = nla_get_u8(tb[NFTA_CT_EXPECT_SIZE]); + if (!priv->size) + priv->size = NF_CT_EXPECT_MAX_CNT; + + err = nf_ct_netns_get(ctx->net, ctx->family); + if (err < 0) + return err; - return nf_ct_netns_get(ctx->net, ctx->family); + err = nft_ct_expect_helper_alloc(priv); + if (err < 0) { + nf_ct_netns_put(ctx->net, ctx->family); + return err; + } + + return err; } static void nft_ct_expect_obj_destroy(const struct nft_ctx *ctx, - struct nft_object *obj) + struct nft_object *obj) { + const struct nft_ct_expect_obj *priv = nft_obj_data(obj); + struct nf_conntrack_helper *me = priv->helper; + + /* This helper is going away, disable it. */ + rcu_assign_pointer(me->help, NULL); + nf_conntrack_helper_release(me); nf_ct_netns_put(ctx->net, ctx->family); } @@ -1297,27 +1406,14 @@ static int nft_ct_expect_obj_dump(struct sk_buff *skb, return 0; } -#if IS_ENABLED(CONFIG_NF_NAT) -static void nft_ct_nat_follow_master(struct nf_conn *ct, struct nf_conntrack_expect *this) -{ - const struct nf_ct_helper_expectfn *expfn; - - expfn = nf_ct_helper_expectfn_find_by_name("nat-follow-master"); - if (expfn) - expfn->expectfn(ct, this); -} -#endif - static void nft_ct_expect_obj_eval(struct nft_object *obj, struct nft_regs *regs, const struct nft_pktinfo *pkt) { const struct nft_ct_expect_obj *priv = nft_obj_data(obj); - struct nf_conntrack_expect *exp; + struct nft_ct_expect_data *expect_data; enum ip_conntrack_info ctinfo; struct nf_conn_help *help; - enum ip_conntrack_dir dir; - u16 l3num = priv->l3num; struct nf_conn *ct; ct = nf_ct_get(pkt->skb, &ctinfo); @@ -1325,45 +1421,30 @@ static void nft_ct_expect_obj_eval(struct nft_object *obj, regs->verdict.code = NFT_BREAK; return; } - dir = CTINFO2DIR(ctinfo); help = nfct_help(ct); - if (!help) - help = nf_ct_helper_ext_add(ct, GFP_ATOMIC); - if (!help) { - regs->verdict.code = NF_DROP; - return; - } - - if (help->expecting[NF_CT_EXPECT_CLASS_DEFAULT] >= priv->size) { + if (help) { regs->verdict.code = NFT_BREAK; return; } - if (l3num == NFPROTO_INET) - l3num = nf_ct_l3num(ct); - exp = nf_ct_expect_alloc(ct); - if (exp == NULL) { + help = nf_ct_helper_ext_add(ct, GFP_ATOMIC); + if (!help) { regs->verdict.code = NF_DROP; return; } - nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, l3num, - &ct->tuplehash[!dir].tuple.src.u3, - &ct->tuplehash[!dir].tuple.dst.u3, - priv->l4proto, NULL, &priv->dport); - exp->timeout += priv->timeout; -#if IS_ENABLED(CONFIG_NF_NAT) - if (ct->status & IPS_NAT_MASK) { - exp->saved_proto.tcp.port = priv->dport; - exp->dir = !dir; - exp->expectfn = nft_ct_nat_follow_master; + expect_data = nfct_help_data(ct); + if (!expect_data) { + regs->verdict.code = NFT_BREAK; + return; } -#endif - if (nf_ct_expect_related(exp, 0) != 0) - regs->verdict.code = NF_DROP; + expect_data->obj = *priv; + expect_data->obj.helper = NULL; + expect_data->dir = CTINFO2DIR(ctinfo); - nf_ct_expect_put(exp); + if (help && refcount_inc_not_zero(&priv->helper->ct_refcnt)) + rcu_assign_pointer(help->helper, priv->helper); } static const struct nla_policy nft_ct_expect_policy[NFTA_CT_EXPECT_MAX + 1] = { -- 2.47.3