From: Jakub Kicinski <kuba@kernel.org>
To: pablo@netfilter.org
Cc: Jakub Kicinski <kuba@kernel.org>,
netfilter-devel@vger.kernel.org, davem@davemloft.net,
netdev@vger.kernel.org, pabeni@redhat.com, edumazet@google.com,
horms@kernel.org, fw@strlen.de, ja@ssi.bg
Subject: Re: [PATCH net-next 11/12] netfilter: nf_conntrack_expect: bail out on insert dead expectations
Date: Tue, 11 Aug 2026 18:34:47 -0700 [thread overview]
Message-ID: <20260812013447.1676073-1-kuba@kernel.org> (raw)
In-Reply-To: <20260810194015.932627-12-pablo@netfilter.org>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
netfilter: nf_conntrack_expect: bail out on insert dead expectations
This adds a check in nf_ct_expect_related_report() that refuses insertion
of an expectation carrying NF_CT_EXPECT_DEAD, returning -EINVAL and
triggering DEBUG_NET_WARN_ON_ONCE(). It is described as hardening for
b8b09dc2bf35 ("netfilter: nf_conntrack_expect: use conntrack GC to reap
expectations").
A couple of questions about the placement of the new check and about the
DEAD flag itself.
> diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c
> index 10b130a7b230b..f1f0c582db5df 100644
> --- a/net/netfilter/nf_conntrack_expect.c
> +++ b/net/netfilter/nf_conntrack_expect.c
> @@ -528,6 +528,12 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect,
> int ret;
>
> spin_lock_bh(&nf_conntrack_expect_lock);
> + if (expect->flags & NF_CT_EXPECT_DEAD) {
> + DEBUG_NET_WARN_ON_ONCE(1);
> + ret = -EINVAL;
> + goto out;
> + }
> +
[Severity: Low]
Would it make sense to put this check in __nf_ct_expect_check() instead?
The other exported insertion entry point, nf_ct_expect_related_pair(),
goes straight from the lock to __nf_ct_expect_check() and
nf_ct_expect_insert() with no NF_CT_EXPECT_DEAD test:
net/netfilter/nf_conntrack_expect.c:nf_ct_expect_related_pair() {
spin_lock_bh(&nf_conntrack_expect_lock);
master_help = nfct_help(expect[0]->master);
if (!master_help || master_help != nfct_help(expect[1]->master)) {
ret = -EINVAL;
goto out;
}
for (i = 0; i < 2; i++) {
ret = __nf_ct_expect_check(expect[i], master_help, flags);
...
}
Since nf_ct_unexpect_related() now only sets the bit and leaves the
expectation linked on hnode/lnode until GC runs:
net/netfilter/nf_conntrack_expect.c:nf_ct_unexpect_related() {
spin_lock_bh(&nf_conntrack_expect_lock);
WRITE_ONCE(exp->flags, exp->flags | NF_CT_EXPECT_DEAD);
spin_unlock_bh(&nf_conntrack_expect_lock);
}
a resubmission of a DEAD-but-still-linked expectation on the pair path
would reach nf_ct_expect_insert() and call hlist_add_head_rcu() on
already-linked nodes, plus double-count cnet->expect_count and
master_help->expecting[class].
The gap in the pair path is pre-existing and not introduced by this patch,
and I could not find any in-tree caller that actually reaches it. The two
pair callers, nf_nat_sdp_media() in net/netfilter/nf_nat_sip.c and
nat_rtp_rtcp() in net/ipv4/netfilter/nf_nat_h323.c, allocate the
expectations per packet with flags cleared, retry only on -EBUSY (before
anything is published), and call nf_ct_unexpect_related() only on the
terminal error exits that return immediately. When the tuple hash is
unchanged, __nf_ct_expect_check()'s own expired-entry loop would also
unlink the stale entry first.
Would it still be worth making the invariant symmetric, given the commit
message says this should not ever happen?
Separately, on the DEAD flag itself:
[Severity: Medium]
This is a pre-existing issue rather than something introduced here, but
since this patch is tightening the semantics of the same flag: is the
kernel-private NF_CT_EXPECT_DEAD bit supposed to be visible over ctnetlink?
The uAPI header keeps it out of the userspace-visible mask:
include/uapi/linux/netfilter/nf_conntrack_common.h {
#ifdef __KERNEL__
#define NF_CT_EXPECT_DEAD 0x8
#define NF_CT_EXPECT_MASK (NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE | \
NF_CT_EXPECT_USERSPACE)
#endif
}
and the input policy enforces that mask, so a request carrying 0x8 is
rejected with -EINVAL:
net/netfilter/nf_conntrack_netlink.c:exp_nla_policy {
[CTA_EXPECT_FLAGS] = NLA_POLICY_MASK(NLA_BE32, NF_CT_EXPECT_MASK),
}
But the dump path exports the raw value:
net/netfilter/nf_conntrack_netlink.c:ctnetlink_exp_dump_expect() {
nla_put_be32(skb, CTA_EXPECT_FLAGS, htonl(exp->flags)) ||
}
Because the expectation now stays linked after nf_ct_unexpect_related()
sets the bit, the value can be observed with 0x8 set through the
IPEXP_DESTROY event from nf_ct_unlink_expect_report(), and through
ctnetlink_exp_ct_dump_table(), which walks help->expectations without the
nf_ct_exp_is_expired() filter that ctnetlink_exp_dump_table() and
__nf_ct_expect_find() apply:
net/netfilter/nf_conntrack_netlink.c:ctnetlink_exp_ct_dump_table() {
hlist_for_each_entry_rcu(exp, &help->expectations, lnode) {
if (l3proto && exp->tuple.src.l3num != l3proto)
continue;
...
}
A tool that reads CTA_EXPECT_FLAGS and replays it gets a value the same
kernel refuses on input. Would masking with NF_CT_EXPECT_MASK in
ctnetlink_exp_dump_expect() be appropriate here?
This behaviour dates back to b8b09dc2bf35 and is unchanged through the end
of the series.
next prev parent reply other threads:[~2026-08-12 1:34 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-10 19:40 [PATCH net-next 00/12] Netfilter updates for net Pablo Neira Ayuso
2026-08-10 19:40 ` [PATCH net-next 01/12] netfilter: add DEBUG_NET_WARN_ON_ONCE to skb_set_nfct() Pablo Neira Ayuso
2026-08-12 1:34 ` Jakub Kicinski
2026-08-10 19:40 ` [PATCH net-next 02/12] net: pass net_device_path_ctx to dev_fill_forward_path() Pablo Neira Ayuso
2026-08-10 19:40 ` [PATCH net-next 03/12] net: netfilter: add ether_type to net_device_path_ctx and use it Pablo Neira Ayuso
2026-08-12 1:34 ` Jakub Kicinski
2026-08-10 19:40 ` [PATCH net-next 04/12] netfilter: flowtable: rename tun.l3_proto to tun.inner_proto Pablo Neira Ayuso
2026-08-10 19:40 ` [PATCH net-next 05/12] netfilter: flowtable: rename ctx.tun.proto to ctx.tun.inner_proto Pablo Neira Ayuso
2026-08-10 19:40 ` [PATCH net-next 06/12] netfilter: flowtable: store ethertype in flowtable context Pablo Neira Ayuso
2026-08-12 1:34 ` Jakub Kicinski
2026-08-10 19:40 ` [PATCH net-next 07/12] netfilter: flowtable: move ipv4 and ipv6 xmit path to function Pablo Neira Ayuso
2026-08-10 19:40 ` [PATCH net-next 08/12] netfilter: flowtable: detach layer 2 encapsulation parser from lookup Pablo Neira Ayuso
2026-08-10 19:40 ` [PATCH net-next 09/12] netfilter: nft_ct: move custom expectation support to helper Pablo Neira Ayuso
2026-08-12 1:34 ` Jakub Kicinski
2026-08-10 19:40 ` [PATCH net-next 10/12] netfilter: conntrack: always lower timeout for non-closing RST packets Pablo Neira Ayuso
2026-08-10 19:40 ` [PATCH net-next 11/12] netfilter: nf_conntrack_expect: bail out on insert dead expectations Pablo Neira Ayuso
2026-08-12 1:34 ` Jakub Kicinski [this message]
2026-08-10 19:40 ` [PATCH net-next 12/12] selftests: netfilter: conntrack_dump_flush: remove unused variables and fix typo Pablo Neira Ayuso
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260812013447.1676073-1-kuba@kernel.org \
--to=kuba@kernel.org \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=fw@strlen.de \
--cc=horms@kernel.org \
--cc=ja@ssi.bg \
--cc=netdev@vger.kernel.org \
--cc=netfilter-devel@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=pablo@netfilter.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox