From 5cb81eeda909dbb2def209dd10636b51549a3f8a Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Sun, 8 Mar 2026 02:21:37 +0900 Subject: [PATCH 01/11] netfilter: ctnetlink: fix use-after-free in ctnetlink_dump_exp_ct() ctnetlink_dump_exp_ct() stores a conntrack pointer in cb->data for the netlink dump callback ctnetlink_exp_ct_dump_table(), but drops the conntrack reference immediately after netlink_dump_start(). When the dump spans multiple rounds, the second recvmsg() triggers the dump callback which dereferences the now-freed conntrack via nfct_help(ct), leading to a use-after-free on ct->ext. The bug is that the netlink_dump_control has no .start or .done callbacks to manage the conntrack reference across dump rounds. Other dump functions in the same file (e.g. ctnetlink_get_conntrack) properly use .start/.done callbacks for this purpose. Fix this by adding .start and .done callbacks that hold and release the conntrack reference for the duration of the dump, and move the nfct_help() call after the cb->args[0] early-return check in the dump callback to avoid dereferencing ct->ext unnecessarily. BUG: KASAN: slab-use-after-free in ctnetlink_exp_ct_dump_table+0x4f/0x2e0 Read of size 8 at addr ffff88810597ebf0 by task ctnetlink_poc/133 CPU: 1 UID: 0 PID: 133 Comm: ctnetlink_poc Not tainted 7.0.0-rc2+ #3 PREEMPTLAZY Call Trace: ctnetlink_exp_ct_dump_table+0x4f/0x2e0 netlink_dump+0x333/0x880 netlink_recvmsg+0x3e2/0x4b0 ? aa_sk_perm+0x184/0x450 sock_recvmsg+0xde/0xf0 Allocated by task 133: kmem_cache_alloc_noprof+0x134/0x440 __nf_conntrack_alloc+0xa8/0x2b0 ctnetlink_create_conntrack+0xa1/0x900 ctnetlink_new_conntrack+0x3cf/0x7d0 nfnetlink_rcv_msg+0x48e/0x510 netlink_rcv_skb+0xc9/0x1f0 nfnetlink_rcv+0xdb/0x220 netlink_unicast+0x3ec/0x590 netlink_sendmsg+0x397/0x690 __sys_sendmsg+0xf4/0x180 Freed by task 0: slab_free_after_rcu_debug+0xad/0x1e0 rcu_core+0x5c3/0x9c0 Fixes: e844a928431f ("netfilter: ctnetlink: allow to dump expectation per master conntrack") Signed-off-by: Hyunwoo Kim Signed-off-by: Florian Westphal --- net/netfilter/nf_conntrack_netlink.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index c9d725fc2d71..65aa44a12d01 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -3212,7 +3212,7 @@ ctnetlink_exp_ct_dump_table(struct sk_buff *skb, struct netlink_callback *cb) { struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh); struct nf_conn *ct = cb->data; - struct nf_conn_help *help = nfct_help(ct); + struct nf_conn_help *help; u_int8_t l3proto = nfmsg->nfgen_family; unsigned long last_id = cb->args[1]; struct nf_conntrack_expect *exp; @@ -3220,6 +3220,10 @@ ctnetlink_exp_ct_dump_table(struct sk_buff *skb, struct netlink_callback *cb) if (cb->args[0]) return 0; + help = nfct_help(ct); + if (!help) + return 0; + rcu_read_lock(); restart: @@ -3249,6 +3253,24 @@ out: return skb->len; } +static int ctnetlink_dump_exp_ct_start(struct netlink_callback *cb) +{ + struct nf_conn *ct = cb->data; + + if (!refcount_inc_not_zero(&ct->ct_general.use)) + return -ENOENT; + return 0; +} + +static int ctnetlink_dump_exp_ct_done(struct netlink_callback *cb) +{ + struct nf_conn *ct = cb->data; + + if (ct) + nf_ct_put(ct); + return 0; +} + static int ctnetlink_dump_exp_ct(struct net *net, struct sock *ctnl, struct sk_buff *skb, const struct nlmsghdr *nlh, @@ -3264,6 +3286,8 @@ static int ctnetlink_dump_exp_ct(struct net *net, struct sock *ctnl, struct nf_conntrack_zone zone; struct netlink_dump_control c = { .dump = ctnetlink_exp_ct_dump_table, + .start = ctnetlink_dump_exp_ct_start, + .done = ctnetlink_dump_exp_ct_done, }; err = ctnetlink_parse_tuple(cda, &tuple, CTA_EXPECT_MASTER, From f900e1d77ee0ef87bfb5ab3fe60f0b3d8ad5ba05 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Tue, 10 Mar 2026 00:28:29 +0100 Subject: [PATCH 02/11] netfilter: conntrack: add missing netlink policy validations Hyunwoo Kim reports out-of-bounds access in sctp and ctnetlink. These attributes are used by the kernel without any validation. Extend the netlink policies accordingly. Quoting the reporter: nlattr_to_sctp() assigns the user-supplied CTA_PROTOINFO_SCTP_STATE value directly to ct->proto.sctp.state without checking that it is within the valid range. [..] and: ... with exp->dir = 100, the access at ct->master->tuplehash[100] reads 5600 bytes past the start of a 320-byte nf_conn object, causing a slab-out-of-bounds read confirmed by UBSAN. Fixes: 076a0ca02644 ("netfilter: ctnetlink: add NAT support for expectations") Fixes: a258860e01b8 ("netfilter: ctnetlink: add full support for SCTP to ctnetlink") Reported-by: Hyunwoo Kim Signed-off-by: Florian Westphal --- net/netfilter/nf_conntrack_netlink.c | 2 +- net/netfilter/nf_conntrack_proto_sctp.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index 65aa44a12d01..c156574e1273 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -3489,7 +3489,7 @@ ctnetlink_change_expect(struct nf_conntrack_expect *x, #if IS_ENABLED(CONFIG_NF_NAT) static const struct nla_policy exp_nat_nla_policy[CTA_EXPECT_NAT_MAX+1] = { - [CTA_EXPECT_NAT_DIR] = { .type = NLA_U32 }, + [CTA_EXPECT_NAT_DIR] = NLA_POLICY_MAX(NLA_BE32, IP_CT_DIR_REPLY), [CTA_EXPECT_NAT_TUPLE] = { .type = NLA_NESTED }, }; #endif diff --git a/net/netfilter/nf_conntrack_proto_sctp.c b/net/netfilter/nf_conntrack_proto_sctp.c index 7c6f7c9f7332..645d2c43ebf7 100644 --- a/net/netfilter/nf_conntrack_proto_sctp.c +++ b/net/netfilter/nf_conntrack_proto_sctp.c @@ -582,7 +582,8 @@ nla_put_failure: } static const struct nla_policy sctp_nla_policy[CTA_PROTOINFO_SCTP_MAX+1] = { - [CTA_PROTOINFO_SCTP_STATE] = { .type = NLA_U8 }, + [CTA_PROTOINFO_SCTP_STATE] = NLA_POLICY_MAX(NLA_U8, + SCTP_CONNTRACK_HEARTBEAT_SENT), [CTA_PROTOINFO_SCTP_VTAG_ORIGINAL] = { .type = NLA_U32 }, [CTA_PROTOINFO_SCTP_VTAG_REPLY] = { .type = NLA_U32 }, }; From fbce58e719a17aa215c724473fd5baaa4a8dc57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Johannes=20M=C3=B6ller?= Date: Tue, 10 Mar 2026 21:49:01 +0000 Subject: [PATCH 03/11] netfilter: nf_conntrack_sip: fix Content-Length u32 truncation in sip_help_tcp() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sip_help_tcp() parses the SIP Content-Length header with simple_strtoul(), which returns unsigned long, but stores the result in unsigned int clen. On 64-bit systems, values exceeding UINT_MAX are silently truncated before computing the SIP message boundary. For example, Content-Length 4294967328 (2^32 + 32) is truncated to 32, causing the parser to miscalculate where the current message ends. The loop then treats trailing data in the TCP segment as a second SIP message and processes it through the SDP parser. Fix this by changing clen to unsigned long to match the return type of simple_strtoul(), and reject Content-Length values that exceed the remaining TCP payload length. Fixes: f5b321bd37fb ("netfilter: nf_conntrack_sip: add TCP support") Signed-off-by: Lukas Johannes Möller Signed-off-by: Florian Westphal --- net/netfilter/nf_conntrack_sip.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index ca748f8dbff1..4ab5ef71d96d 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -1534,11 +1534,12 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff, { struct tcphdr *th, _tcph; unsigned int dataoff, datalen; - unsigned int matchoff, matchlen, clen; + unsigned int matchoff, matchlen; unsigned int msglen, origlen; const char *dptr, *end; s16 diff, tdiff = 0; int ret = NF_ACCEPT; + unsigned long clen; bool term; if (ctinfo != IP_CT_ESTABLISHED && @@ -1573,6 +1574,9 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff, if (dptr + matchoff == end) break; + if (clen > datalen) + break; + term = false; for (; end + strlen("\r\n\r\n") <= dptr + datalen; end++) { if (end[0] == '\r' && end[1] == '\n' && From 598adea720b97572c7028635cb1c59b3684e128c Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Wed, 11 Mar 2026 16:24:02 +0100 Subject: [PATCH 04/11] netfilter: revert nft_set_rbtree: validate open interval overlap This reverts commit 648946966a08 ("netfilter: nft_set_rbtree: validate open interval overlap"). There have been reports of nft failing to laod valid rulesets after this patch was merged into -stable. I can reproduce several such problem with recent nft versions, including nft 1.1.6 which is widely shipped by distributions. We currently have little choice here. This commit can be resurrected at some point once the nftables fix that triggers the false overlap positive has appeared in common distros (see e83e32c8d1cd ("mnl: restore create element command with large batches" in nftables.git). Fixes: 648946966a08 ("netfilter: nft_set_rbtree: validate open interval overlap") Acked-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal --- include/net/netfilter/nf_tables.h | 4 -- net/netfilter/nf_tables_api.c | 21 ++------- net/netfilter/nft_set_rbtree.c | 71 +++++-------------------------- 3 files changed, 14 insertions(+), 82 deletions(-) diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index e2d2bfc1f989..6299af4ef423 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -277,8 +277,6 @@ struct nft_userdata { unsigned char data[]; }; -#define NFT_SET_ELEM_INTERNAL_LAST 0x1 - /* placeholder structure for opaque set element backend representation. */ struct nft_elem_priv { }; @@ -288,7 +286,6 @@ struct nft_elem_priv { }; * @key: element key * @key_end: closing element key * @data: element data - * @flags: flags * @priv: element private data and extensions */ struct nft_set_elem { @@ -304,7 +301,6 @@ struct nft_set_elem { u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)]; struct nft_data val; } data; - u32 flags; struct nft_elem_priv *priv; }; diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index dacec5f8a11c..4ccdd33cf133 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -7156,8 +7156,7 @@ static u32 nft_set_maxsize(const struct nft_set *set) } static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, - const struct nlattr *attr, u32 nlmsg_flags, - bool last) + const struct nlattr *attr, u32 nlmsg_flags) { struct nft_expr *expr_array[NFT_SET_EXPR_MAX] = {}; struct nlattr *nla[NFTA_SET_ELEM_MAX + 1]; @@ -7444,11 +7443,6 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, if (flags) *nft_set_ext_flags(ext) = flags; - if (last) - elem.flags = NFT_SET_ELEM_INTERNAL_LAST; - else - elem.flags = 0; - if (obj) *nft_set_ext_obj(ext) = obj; @@ -7613,8 +7607,7 @@ static int nf_tables_newsetelem(struct sk_buff *skb, nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) { - err = nft_add_set_elem(&ctx, set, attr, info->nlh->nlmsg_flags, - nla_is_last(attr, rem)); + err = nft_add_set_elem(&ctx, set, attr, info->nlh->nlmsg_flags); if (err < 0) { NL_SET_BAD_ATTR(extack, attr); return err; @@ -7738,7 +7731,7 @@ static void nft_trans_elems_destroy_abort(const struct nft_ctx *ctx, } static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, - const struct nlattr *attr, bool last) + const struct nlattr *attr) { struct nlattr *nla[NFTA_SET_ELEM_MAX + 1]; struct nft_set_ext_tmpl tmpl; @@ -7806,11 +7799,6 @@ static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, if (flags) *nft_set_ext_flags(ext) = flags; - if (last) - elem.flags = NFT_SET_ELEM_INTERNAL_LAST; - else - elem.flags = 0; - trans = nft_trans_elem_alloc(ctx, NFT_MSG_DELSETELEM, set); if (trans == NULL) goto fail_trans; @@ -7961,8 +7949,7 @@ static int nf_tables_delsetelem(struct sk_buff *skb, return nft_set_flush(&ctx, set, genmask); nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) { - err = nft_del_setelem(&ctx, set, attr, - nla_is_last(attr, rem)); + err = nft_del_setelem(&ctx, set, attr); if (err == -ENOENT && NFNL_MSG_TYPE(info->nlh->nlmsg_type) == NFT_MSG_DESTROYSETELEM) continue; diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index ee3d4f5b9ff7..fe8bd497d74a 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -304,19 +304,10 @@ static void nft_rbtree_set_start_cookie(struct nft_rbtree *priv, priv->start_rbe_cookie = (unsigned long)rbe; } -static void nft_rbtree_set_start_cookie_open(struct nft_rbtree *priv, - const struct nft_rbtree_elem *rbe, - unsigned long open_interval) -{ - priv->start_rbe_cookie = (unsigned long)rbe | open_interval; -} - -#define NFT_RBTREE_OPEN_INTERVAL 1UL - static bool nft_rbtree_cmp_start_cookie(struct nft_rbtree *priv, const struct nft_rbtree_elem *rbe) { - return (priv->start_rbe_cookie & ~NFT_RBTREE_OPEN_INTERVAL) == (unsigned long)rbe; + return priv->start_rbe_cookie == (unsigned long)rbe; } static bool nft_rbtree_insert_same_interval(const struct net *net, @@ -346,14 +337,13 @@ static bool nft_rbtree_insert_same_interval(const struct net *net, static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, struct nft_rbtree_elem *new, - struct nft_elem_priv **elem_priv, u64 tstamp, bool last) + struct nft_elem_priv **elem_priv, u64 tstamp) { struct nft_rbtree_elem *rbe, *rbe_le = NULL, *rbe_ge = NULL, *rbe_prev; struct rb_node *node, *next, *parent, **p, *first = NULL; struct nft_rbtree *priv = nft_set_priv(set); u8 cur_genmask = nft_genmask_cur(net); u8 genmask = nft_genmask_next(net); - unsigned long open_interval = 0; int d; /* Descend the tree to search for an existing element greater than the @@ -459,18 +449,10 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, } } - if (nft_rbtree_interval_null(set, new)) { + if (nft_rbtree_interval_null(set, new)) + priv->start_rbe_cookie = 0; + else if (nft_rbtree_interval_start(new) && priv->start_rbe_cookie) priv->start_rbe_cookie = 0; - } else if (nft_rbtree_interval_start(new) && priv->start_rbe_cookie) { - if (nft_set_is_anonymous(set)) { - priv->start_rbe_cookie = 0; - } else if (priv->start_rbe_cookie & NFT_RBTREE_OPEN_INTERVAL) { - /* Previous element is an open interval that partially - * overlaps with an existing non-open interval. - */ - return -ENOTEMPTY; - } - } /* - new start element matching existing start element: full overlap * reported as -EEXIST, cleared by caller if NLM_F_EXCL is not given. @@ -478,27 +460,7 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, if (rbe_ge && !nft_rbtree_cmp(set, new, rbe_ge) && nft_rbtree_interval_start(rbe_ge) == nft_rbtree_interval_start(new)) { *elem_priv = &rbe_ge->priv; - - /* - Corner case: new start element of open interval (which - * comes as last element in the batch) overlaps the start of - * an existing interval with an end element: partial overlap. - */ - node = rb_first(&priv->root); - rbe = __nft_rbtree_next_active(node, genmask); - if (rbe && nft_rbtree_interval_end(rbe)) { - rbe = nft_rbtree_next_active(rbe, genmask); - if (rbe && - nft_rbtree_interval_start(rbe) && - !nft_rbtree_cmp(set, new, rbe)) { - if (last) - return -ENOTEMPTY; - - /* Maybe open interval? */ - open_interval = NFT_RBTREE_OPEN_INTERVAL; - } - } - nft_rbtree_set_start_cookie_open(priv, rbe_ge, open_interval); - + nft_rbtree_set_start_cookie(priv, rbe_ge); return -EEXIST; } @@ -553,12 +515,6 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, nft_rbtree_interval_end(rbe_ge) && nft_rbtree_interval_end(new)) return -ENOTEMPTY; - /* - start element overlaps an open interval but end element is new: - * partial overlap, reported as -ENOEMPTY. - */ - if (!rbe_ge && priv->start_rbe_cookie && nft_rbtree_interval_end(new)) - return -ENOTEMPTY; - /* Accepted element: pick insertion point depending on key value */ parent = NULL; p = &priv->root.rb_node; @@ -668,7 +624,6 @@ static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, struct nft_elem_priv **elem_priv) { struct nft_rbtree_elem *rbe = nft_elem_priv_cast(elem->priv); - bool last = !!(elem->flags & NFT_SET_ELEM_INTERNAL_LAST); struct nft_rbtree *priv = nft_set_priv(set); u64 tstamp = nft_net_tstamp(net); int err; @@ -685,12 +640,8 @@ static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, cond_resched(); write_lock_bh(&priv->lock); - err = __nft_rbtree_insert(net, set, rbe, elem_priv, tstamp, last); + err = __nft_rbtree_insert(net, set, rbe, elem_priv, tstamp); write_unlock_bh(&priv->lock); - - if (nft_rbtree_interval_end(rbe)) - priv->start_rbe_cookie = 0; - } while (err == -EAGAIN); return err; @@ -778,7 +729,6 @@ nft_rbtree_deactivate(const struct net *net, const struct nft_set *set, const struct nft_set_elem *elem) { struct nft_rbtree_elem *rbe, *this = nft_elem_priv_cast(elem->priv); - bool last = !!(elem->flags & NFT_SET_ELEM_INTERNAL_LAST); struct nft_rbtree *priv = nft_set_priv(set); const struct rb_node *parent = priv->root.rb_node; u8 genmask = nft_genmask_next(net); @@ -819,10 +769,9 @@ nft_rbtree_deactivate(const struct net *net, const struct nft_set *set, continue; } - if (nft_rbtree_interval_start(rbe)) { - if (!last) - nft_rbtree_set_start_cookie(priv, rbe); - } else if (!nft_rbtree_deactivate_same_interval(net, priv, rbe)) + if (nft_rbtree_interval_start(rbe)) + nft_rbtree_set_start_cookie(priv, rbe); + else if (!nft_rbtree_deactivate_same_interval(net, priv, rbe)) return NULL; nft_rbtree_flush(net, set, &rbe->priv); From a3aca98aec9a278ee56da4f8013bfa1dd1a1c298 Mon Sep 17 00:00:00 2001 From: Eric Woudstra Date: Tue, 10 Mar 2026 15:39:33 +0100 Subject: [PATCH 05/11] netfilter: nf_flow_table_ip: reset mac header before vlan push With double vlan tagged packets in the fastpath, getting the error: skb_vlan_push got skb with skb->data not at mac header (offset 18) Call skb_reset_mac_header() before calling skb_vlan_push(). Fixes: c653d5a78f34 ("netfilter: flowtable: inline vlan encapsulation in xmit path") Signed-off-by: Eric Woudstra Acked-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal --- net/netfilter/nf_flow_table_ip.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index 3fdb10d9bf7f..fd56d663cb5b 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -738,6 +738,7 @@ static int nf_flow_encap_push(struct sk_buff *skb, switch (tuple->encap[i].proto) { case htons(ETH_P_8021Q): case htons(ETH_P_8021AD): + skb_reset_mac_header(skb); if (skb_vlan_push(skb, tuple->encap[i].proto, tuple->encap[i].id) < 0) return -1; From 1e3a3593162c96e8a8de48b1e14f60c3b57fca8a Mon Sep 17 00:00:00 2001 From: Jenny Guanni Qu Date: Thu, 12 Mar 2026 02:29:32 +0000 Subject: [PATCH 06/11] netfilter: nf_conntrack_h323: fix OOB read in decode_int() CONS case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In decode_int(), the CONS case calls get_bits(bs, 2) to read a length value, then calls get_uint(bs, len) without checking that len bytes remain in the buffer. The existing boundary check only validates the 2 bits for get_bits(), not the subsequent 1-4 bytes that get_uint() reads. This allows a malformed H.323/RAS packet to cause a 1-4 byte slab-out-of-bounds read. Add a boundary check for len bytes after get_bits() and before get_uint(). Fixes: 5e35941d9901 ("[NETFILTER]: Add H.323 conntrack/NAT helper") Reported-by: Klaudia Kloc Reported-by: Dawid Moczadło Signed-off-by: Jenny Guanni Qu Signed-off-by: Florian Westphal --- net/netfilter/nf_conntrack_h323_asn1.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/netfilter/nf_conntrack_h323_asn1.c b/net/netfilter/nf_conntrack_h323_asn1.c index 62aa22a07876..c972e9488e16 100644 --- a/net/netfilter/nf_conntrack_h323_asn1.c +++ b/net/netfilter/nf_conntrack_h323_asn1.c @@ -331,6 +331,8 @@ static int decode_int(struct bitstr *bs, const struct field_t *f, if (nf_h323_error_boundary(bs, 0, 2)) return H323_ERROR_BOUND; len = get_bits(bs, 2) + 1; + if (nf_h323_error_boundary(bs, len, 0)) + return H323_ERROR_BOUND; BYTE_ALIGN(bs); if (base && (f->attr & DECODE)) { /* timeToLive */ unsigned int v = get_uint(bs, len) + f->lb; From 0548a13b5a145b16e4da0628b5936baf35f51b43 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 12 Mar 2026 12:38:59 +0100 Subject: [PATCH 07/11] nf_tables: nft_dynset: fix possible stateful expression memleak in error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If cloning the second stateful expression in the element via GFP_ATOMIC fails, then the first stateful expression remains in place without being released.   unreferenced object (percpu) 0x607b97e9cab8 (size 16):     comm "softirq", pid 0, jiffies 4294931867     hex dump (first 16 bytes on cpu 3):       00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     backtrace (crc 0):       pcpu_alloc_noprof+0x453/0xd80       nft_counter_clone+0x9c/0x190 [nf_tables]       nft_expr_clone+0x8f/0x1b0 [nf_tables]       nft_dynset_new+0x2cb/0x5f0 [nf_tables]       nft_rhash_update+0x236/0x11c0 [nf_tables]       nft_dynset_eval+0x11f/0x670 [nf_tables]       nft_do_chain+0x253/0x1700 [nf_tables]       nft_do_chain_ipv4+0x18d/0x270 [nf_tables]       nf_hook_slow+0xaa/0x1e0       ip_local_deliver+0x209/0x330 Fixes: 563125a73ac3 ("netfilter: nftables: generalize set extension to support for several expressions") Reported-by: Gurpreet Shergill Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal --- include/net/netfilter/nf_tables.h | 2 ++ net/netfilter/nf_tables_api.c | 4 ++-- net/netfilter/nft_dynset.c | 10 +++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 6299af4ef423..ec8a8ec9c0aa 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -874,6 +874,8 @@ struct nft_elem_priv *nft_set_elem_init(const struct nft_set *set, u64 timeout, u64 expiration, gfp_t gfp); int nft_set_elem_expr_clone(const struct nft_ctx *ctx, struct nft_set *set, struct nft_expr *expr_array[]); +void nft_set_elem_expr_destroy(const struct nft_ctx *ctx, + struct nft_set_elem_expr *elem_expr); void nft_set_elem_destroy(const struct nft_set *set, const struct nft_elem_priv *elem_priv, bool destroy_expr); diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 4ccdd33cf133..9b1c8d0a35fb 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -6744,8 +6744,8 @@ static void __nft_set_elem_expr_destroy(const struct nft_ctx *ctx, } } -static void nft_set_elem_expr_destroy(const struct nft_ctx *ctx, - struct nft_set_elem_expr *elem_expr) +void nft_set_elem_expr_destroy(const struct nft_ctx *ctx, + struct nft_set_elem_expr *elem_expr) { struct nft_expr *expr; u32 size; diff --git a/net/netfilter/nft_dynset.c b/net/netfilter/nft_dynset.c index 7807d8129664..9123277be03c 100644 --- a/net/netfilter/nft_dynset.c +++ b/net/netfilter/nft_dynset.c @@ -30,18 +30,26 @@ static int nft_dynset_expr_setup(const struct nft_dynset *priv, const struct nft_set_ext *ext) { struct nft_set_elem_expr *elem_expr = nft_set_ext_expr(ext); + struct nft_ctx ctx = { + .net = read_pnet(&priv->set->net), + .family = priv->set->table->family, + }; struct nft_expr *expr; int i; for (i = 0; i < priv->num_exprs; i++) { expr = nft_setelem_expr_at(elem_expr, elem_expr->size); if (nft_expr_clone(expr, priv->expr_array[i], GFP_ATOMIC) < 0) - return -1; + goto err_out; elem_expr->size += priv->expr_array[i]->ops->size; } return 0; +err_out: + nft_set_elem_expr_destroy(&ctx, elem_expr); + + return -1; } struct nft_elem_priv *nft_dynset_new(struct nft_set *set, From 36eae0956f659e48d5366d9b083d9417f3263ddc Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 12 Mar 2026 13:48:47 +0100 Subject: [PATCH 08/11] netfilter: nft_ct: drop pending enqueued packets on removal Packets sitting in nfqueue might hold a reference to: - templates that specify the conntrack zone, because a percpu area is used and module removal is possible. - conntrack timeout policies and helper, where object removal leave a stale reference. Since these objects can just go away, drop enqueued packets to avoid stale reference to them. If there is a need for finer grain removal, this logic can be revisited to make selective packet drop upon dependencies. Fixes: 7e0b2b57f01d ("netfilter: nft_ct: add ct timeout support") Reported-by: Yiming Qian Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal --- net/netfilter/nft_ct.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c index 47d3ef109a99..128ff8155b5d 100644 --- a/net/netfilter/nft_ct.c +++ b/net/netfilter/nft_ct.c @@ -23,6 +23,7 @@ #include #include #include +#include "nf_internals.h" struct nft_ct_helper_obj { struct nf_conntrack_helper *helper4; @@ -543,6 +544,7 @@ static void __nft_ct_set_destroy(const struct nft_ctx *ctx, struct nft_ct *priv) #endif #ifdef CONFIG_NF_CONNTRACK_ZONES case NFT_CT_ZONE: + nf_queue_nf_hook_drop(ctx->net); mutex_lock(&nft_ct_pcpu_mutex); if (--nft_ct_pcpu_template_refcnt == 0) nft_ct_tmpl_put_pcpu(); @@ -1015,6 +1017,7 @@ static void nft_ct_timeout_obj_destroy(const struct nft_ctx *ctx, struct nft_ct_timeout_obj *priv = nft_obj_data(obj); struct nf_ct_timeout *timeout = priv->timeout; + nf_queue_nf_hook_drop(ctx->net); nf_ct_untimeout(ctx->net, timeout); nf_ct_netns_put(ctx->net, ctx->family); kfree(priv->timeout); @@ -1147,6 +1150,7 @@ static void nft_ct_helper_obj_destroy(const struct nft_ctx *ctx, { struct nft_ct_helper_obj *priv = nft_obj_data(obj); + nf_queue_nf_hook_drop(ctx->net); if (priv->helper4) nf_conntrack_helper_put(priv->helper4); if (priv->helper6) From f62a218a946b19bb59abdd5361da85fa4606b96b Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 12 Mar 2026 13:48:48 +0100 Subject: [PATCH 09/11] netfilter: xt_CT: drop pending enqueued packets on template removal Templates refer to objects that can go away while packets are sitting in nfqueue refer to: - helper, this can be an issue on module removal. - timeout policy, nfnetlink_cttimeout might remove it. The use of templates with zone and event cache filter are safe, since this just copies values. Flush these enqueued packets in case the template rule gets removed. Fixes: 24de58f46516 ("netfilter: xt_CT: allow to attach timeout policy + glue code") Reported-by: Yiming Qian Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal --- net/netfilter/xt_CT.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/netfilter/xt_CT.c b/net/netfilter/xt_CT.c index 3ba94c34297c..498f5871c84a 100644 --- a/net/netfilter/xt_CT.c +++ b/net/netfilter/xt_CT.c @@ -16,6 +16,7 @@ #include #include #include +#include "nf_internals.h" static inline int xt_ct_target(struct sk_buff *skb, struct nf_conn *ct) { @@ -283,6 +284,9 @@ static void xt_ct_tg_destroy(const struct xt_tgdtor_param *par, struct nf_conn_help *help; if (ct) { + if (info->helper[0] || info->timeout[0]) + nf_queue_nf_hook_drop(par->net); + help = nfct_help(ct); xt_ct_put_helper(help); From 00050ec08cecfda447e1209b388086d76addda3a Mon Sep 17 00:00:00 2001 From: Jenny Guanni Qu Date: Thu, 12 Mar 2026 14:59:49 +0000 Subject: [PATCH 10/11] netfilter: xt_time: use unsigned int for monthday bit shift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The monthday field can be up to 31, and shifting a signed integer 1 by 31 positions (1 << 31) is undefined behavior in C, as the result overflows a 32-bit signed int. Use 1U to ensure well-defined behavior for all valid monthday values. Change the weekday shift to 1U as well for consistency. Fixes: ee4411a1b1e0 ("[NETFILTER]: x_tables: add xt_time match") Reported-by: Klaudia Kloc Reported-by: Dawid Moczadło Tested-by: Jenny Guanni Qu Signed-off-by: Jenny Guanni Qu Signed-off-by: Florian Westphal --- net/netfilter/xt_time.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/netfilter/xt_time.c b/net/netfilter/xt_time.c index 00319d2a54da..d9d74011bb64 100644 --- a/net/netfilter/xt_time.c +++ b/net/netfilter/xt_time.c @@ -223,13 +223,13 @@ time_mt(const struct sk_buff *skb, struct xt_action_param *par) localtime_2(¤t_time, stamp); - if (!(info->weekdays_match & (1 << current_time.weekday))) + if (!(info->weekdays_match & (1U << current_time.weekday))) return false; /* Do not spend time computing monthday if all days match anyway */ if (info->monthdays_match != XT_TIME_ALL_MONTHDAYS) { localtime_3(¤t_time, stamp); - if (!(info->monthdays_match & (1 << current_time.monthday))) + if (!(info->monthdays_match & (1U << current_time.monthday))) return false; } From f173d0f4c0f689173f8cdac79991043a4a89bf66 Mon Sep 17 00:00:00 2001 From: Jenny Guanni Qu Date: Thu, 12 Mar 2026 14:49:50 +0000 Subject: [PATCH 11/11] netfilter: nf_conntrack_h323: check for zero length in DecodeQ931() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In DecodeQ931(), the UserUserIE code path reads a 16-bit length from the packet, then decrements it by 1 to skip the protocol discriminator byte before passing it to DecodeH323_UserInformation(). If the encoded length is 0, the decrement wraps to -1, which is then passed as a large value to the decoder, leading to an out-of-bounds read. Add a check to ensure len is positive after the decrement. Fixes: 5e35941d9901 ("[NETFILTER]: Add H.323 conntrack/NAT helper") Reported-by: Klaudia Kloc Reported-by: Dawid Moczadło Tested-by: Jenny Guanni Qu Signed-off-by: Jenny Guanni Qu Signed-off-by: Florian Westphal --- net/netfilter/nf_conntrack_h323_asn1.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/netfilter/nf_conntrack_h323_asn1.c b/net/netfilter/nf_conntrack_h323_asn1.c index c972e9488e16..7b1497ed97d2 100644 --- a/net/netfilter/nf_conntrack_h323_asn1.c +++ b/net/netfilter/nf_conntrack_h323_asn1.c @@ -924,6 +924,8 @@ int DecodeQ931(unsigned char *buf, size_t sz, Q931 *q931) break; p++; len--; + if (len <= 0) + break; return DecodeH323_UserInformation(buf, p, len, &q931->UUIE); }