[PATCH nf] netfilter: nf_conncount: fix zone comparison in tuple dedup
From: Yizhou Zhao <hidden>
Date: 2026-07-06 11:48:44
Also in:
lkml, netfilter-devel, stable
Subsystem:
netfilter, networking [general], the rest · Maintainers:
Pablo Neira Ayuso, Florian Westphal, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
The "already exists" dedup logic in __nf_conncount_add() decides
whether a connection has already been counted and can be skipped instead
of incrementing the connlimit count. It compares the conntrack zone of a
list entry with the zone of the connection being added using
nf_ct_zone_id() and nf_ct_zone_equal(), passing conn->zone.dir or
zone->dir as the direction argument.
Those helpers take enum ip_conntrack_dir values: IP_CT_DIR_ORIGINAL is 0
and IP_CT_DIR_REPLY is 1. However, zone->dir is a u8 bitmask:
NF_CT_ZONE_DIR_ORIG is 1, NF_CT_ZONE_DIR_REPL is 2 and
NF_CT_DEFAULT_ZONE_DIR is 3. Passing that bitmask as the enum direction
shifts the meaning of every non-zero value. An ORIG-only zone passes 1
and is tested as REPLY, while REPL-only and default zones pass 2 or 3 and
test bits beyond the valid direction range. In those cases
nf_ct_zone_id() can fall back to NF_CT_DEFAULT_ZONE_ID instead of using
the real zone id, so different zones can be treated as equal and dedup
collapses to tuple equality alone.
Do not special-case NF_CT_DEFAULT_ZONE_DIR and do not compare raw zone
ids: that would address only the common bidirectional case and would
bypass the direction-aware NF_CT_DEFAULT_ZONE_ID fallback. Instead, add
a small conncount-local helper that converts the zone direction bitmask
to a valid enum direction before calling the existing zone helpers. A
default bidirectional zone contains the ORIG bit, so it naturally maps to
IP_CT_DIR_ORIGINAL; single-direction zones continue to use the existing
nf_ct_zone_id() fallback semantics.
Fixes: 21ba8847f857 ("netfilter: nf_conncount: Fix garbage collection with zones")
Fixes: b36e4523d4d5 ("netfilter: nf_conncount: fix garbage collection confirm race")
Cc: stable@vger.kernel.org
Reported-by: Yizhou Zhao <redacted>
Reported-by: Yuxiang Yang <redacted>
Reported-by: Ao Wang <redacted>
Reported-by: Xuewei Feng <redacted>
Reported-by: Qi Li <redacted>
Reported-by: Ke Xu <redacted>
Assisted-by: Claude-Code:GLM-5.2
Signed-off-by: Yizhou Zhao <redacted>
---diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c
index 91582069f6d2..eb3156782405 100644
--- a/net/netfilter/nf_conncount.c
+++ b/net/netfilter/nf_conncount.c@@ -127,6 +127,15 @@ find_or_evict(struct net *net, struct nf_conncount_list *list, return ERR_PTR(-EAGAIN); } +static enum ip_conntrack_dir +nf_conncount_zone_dir(const struct nf_conntrack_zone *zone) +{ + if (zone->dir & NF_CT_ZONE_DIR_ORIG) + return IP_CT_DIR_ORIGINAL; + + return IP_CT_DIR_REPLY; +} + static bool get_ct_or_tuple_from_skb(struct net *net, const struct sk_buff *skb, u16 l3num,
@@ -211,8 +220,10 @@ static int __nf_conncount_add(struct net *net, /* Not found, but might be about to be confirmed */ if (PTR_ERR(found) == -EAGAIN) { if (nf_ct_tuple_equal(&conn->tuple, &tuple) && - nf_ct_zone_id(&conn->zone, conn->zone.dir) == - nf_ct_zone_id(zone, zone->dir)) + nf_ct_zone_id(&conn->zone, + nf_conncount_zone_dir(&conn->zone)) == + nf_ct_zone_id(zone, + nf_conncount_zone_dir(zone))) goto out_put; /* already exists */ } else { collect++;
@@ -223,7 +234,7 @@ static int __nf_conncount_add(struct net *net, found_ct = nf_ct_tuplehash_to_ctrack(found); if (nf_ct_tuple_equal(&conn->tuple, &tuple) && - nf_ct_zone_equal(found_ct, zone, zone->dir)) { + nf_ct_zone_equal(found_ct, zone, nf_conncount_zone_dir(zone))) { /* * We should not see tuples twice unless someone hooks * this into a table without "-p tcp --syn". --
2.47.3