nft_ct_timeout_obj_eval() only compares the timeout object protocol with packet metadata. A packet header can be changed after conntrack attaches an entry, so this metadata does not necessarily describe the entry. Timeout objects contain protocol-specific arrays. Attaching an object for a protocol with fewer timeout states to an entry for one with more states lets the conntrack tracker read beyond the object. Require the object protocol to match the conntrack tuple protocol before attaching it. This mirrors validation by named timeout policies and nftables conntrack helper objects. Based on original patch from Kyle Zheng, who also authored this commit message. LLM review complains about _ext_add() races with cloned unconfirmed skbs. conntrack never supported this; fixing it is hard and out of scope for this change. Fixes: 0434ccdcf883 ("netfilter: nf_tables: rework ct timeout set support") Signed-off-by: Kyle Zeng Signed-off-by: Florian Westphal --- v2: keep nft_ct expectation as is, LLM claims this broke nft expectation support. changelog: I'm too stupid to fix all bugs at once. net/netfilter/nft_ct.c | 48 +++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c index a1093311414b..1cecdcae1f4e 100644 --- a/net/netfilter/nft_ct.c +++ b/net/netfilter/nft_ct.c @@ -839,6 +839,38 @@ static struct nft_expr_type nft_notrack_type __read_mostly = { .owner = THIS_MODULE, }; +/** + * nft_ct_get_safe() - Return nf_conn with extra checks + * @pkt: nftables packet information structure + * @l4proto: The expected Layer 4 protocol + * @ctinfop: packet ip_conntrack_info storage + * + * Returns the conntrack entry only if it is unconfirmed, non-template and + * matches the expected L4 protocol. + * + * Return: Pointer to the &struct nf_conn if all checks pass; NULL otherwise. + */ +static struct nf_conn *nft_ct_get_safe(const struct nft_pktinfo *pkt, + u8 l4proto, enum ip_conntrack_info *ctinfop) +{ + enum ip_conntrack_info ctinfo; + struct nf_conn *ct; + + ct = nf_ct_get(pkt->skb, &ctinfo); + if (!ct || l4proto != pkt->tprot) + return NULL; + + if (l4proto != nf_ct_protonum(ct)) + return NULL; + + if (READ_ONCE(ct->status) & (IPS_TEMPLATE | IPS_CONFIRMED)) + return NULL; + + if (ctinfop) + *ctinfop = ctinfo; + return ct; +} + #ifdef CONFIG_NF_CONNTRACK_TIMEOUT static int nft_ct_timeout_parse_policy(void *timeouts, @@ -878,14 +910,12 @@ static void nft_ct_timeout_obj_eval(struct nft_object *obj, const struct nft_pktinfo *pkt) { const struct nft_ct_timeout_obj *priv = nft_obj_data(obj); - struct nf_conn *ct = (struct nf_conn *)skb_nfct(pkt->skb); struct nf_conn_timeout *timeout; const unsigned int *values; + struct nf_conn *ct; - if (priv->l4proto != pkt->tprot) - return; - - if (!ct || nf_ct_is_template(ct) || nf_ct_is_confirmed(ct)) + ct = nft_ct_get_safe(pkt, priv->l4proto, NULL); + if (!ct) return; timeout = nf_ct_timeout_find(ct); @@ -1114,14 +1144,12 @@ static void nft_ct_helper_obj_eval(struct nft_object *obj, const struct nft_pktinfo *pkt) { const struct nft_ct_helper_obj *priv = nft_obj_data(obj); - struct nf_conn *ct = (struct nf_conn *)skb_nfct(pkt->skb); struct nf_conntrack_helper *to_assign = NULL; struct nf_conn_help *help; + struct nf_conn *ct; - if (!ct || - nf_ct_is_confirmed(ct) || - nf_ct_is_template(ct) || - priv->l4proto != nf_ct_protonum(ct)) + ct = nft_ct_get_safe(pkt, priv->l4proto, NULL); + if (!ct) return; switch (nf_ct_l3num(ct)) { -- 2.55.0