From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 23F8B4137AE for ; Sat, 8 Aug 2026 14:09:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786198195; cv=none; b=jaNH77DHOrPm17jO6RlU/b+d6iAp9OT7i0zQgEbDN35casDDI3xslMmlinLPoX3cxeT8PrlZDG2BLxh/RVIqVYiVh/BN7Z9Ummxa4RglkpwK437cEPZc3IpbIyCoVbflIf69yAVwuNROuiHKAOrDc/txdkEfU2dSm4G3SDDi3C4= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786198195; c=relaxed/simple; bh=L1PH7FJk7Sc0NZclNqR38U48MRaILAnyRLF8+pfGdwg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=tweFy79VfrsXvq8/p8lrBdtDsaNDWTGxWnHPjfFBCMSf7bqhD6QiGLYQzK3OeIr3EJTFXHvjmsbsKVelGLMVk2tRSq72CH/gP9ZhQEGR9rNszCLF31xvSAK2mltRg7q7BOdg2+tT+0M84RthWS27eIWmB2UwoXhA/dn+mkt9RAE= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=XWgcyxlO; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="XWgcyxlO" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0B88B1F00A3F; Sat, 8 Aug 2026 14:09:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786198193; bh=f6z+DauANsA6iYVOJd8QK6AMopD0mpGg07WgcLLpxtY=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=XWgcyxlOgg8vit/SR9TFf+tzCe0MBasTtvTYqKGnX4tuWTEGsrF4iFGgxEbiIvoaG rdJYjtR/6MJih+cGqwmIsmmK2daW5VYEtoAQTzaQmRExJ7KeBK1VC0kJCg82nG0nTK O+vj3pTqVLkdN039ymUtTB/sDicVvIkR85Z8XTVYCnaOonXGtRgUFcnXeG/eadIRpM vslIOLUMOOwND5Yah/ldgC4HEMkSocZKCPdo2vhEbznlYD3LBUmnYdvOPu56dCf8Bd a4SCiYBAJ3T3s7RJbIniqI8c/j67YRWdGzPxHgkRb9xim50qtopC5IHhSKO9aRNXPD 9z5lv6e1LB7lg== From: Sasha Levin To: stable@vger.kernel.org Cc: Aldo Ariel Panzardo , Eric Dumazet , Jamal Hadi Salim , Jakub Kicinski , Sasha Levin Subject: [PATCH 6.6.y] net/sched: serialize qdisc_rtab_list against concurrent get/put Date: Sat, 8 Aug 2026 10:09:51 -0400 Message-ID: <20260808140951.340551-1-sashal@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <2026072926-crane-private-917f@gregkh> References: <2026072926-crane-private-917f@gregkh> Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Aldo Ariel Panzardo [ Upstream commit f43ee0c0730d6191629b5ee1ceae27b1ebfdc047 ] qdisc_get_rtab() and qdisc_put_rtab() mutate the process-global singly linked list qdisc_rtab_list and a plain non-atomic 'int refcnt' with no lock. This was only safe because every caller historically held the RTNL mutex, which serialized all rate-table lookups, inserts and frees. That invariant no longer holds. cls_flower sets TCF_PROTO_OPS_DOIT_UNLOCKED, so tc_new_tfilter() keeps rtnl_held == false for it and sets TCA_ACT_FLAGS_NO_RTNL. That flag propagates through tcf_exts_validate_ex() -> tcf_action_init() -> tcf_action_init_1() -> tcf_police_init(), which calls qdisc_get_rtab()/qdisc_put_rtab() with the RTNL mutex NOT held. Two RTM_NEWTFILTER requests on different CPUs, each adding a flower filter with a police action carrying the same rate, then race on qdisc_rtab_list and on the non-atomic refcnt, leading to a use-after-free / double-free of the kmalloc-2k struct qdisc_rate_table. qdisc_rtab_list is a single global (not per-netns), so the corrupted object is shared system-wide. BUG: KASAN: slab-use-after-free in qdisc_put_rtab+0x12f/0x160 qdisc_put_rtab+0x12f/0x160 tcf_police_init+0xda9/0x1590 tcf_action_init_1+0x460/0x6b0 tcf_action_init+0x439/0xa40 tcf_exts_validate_ex+0x42d/0x550 fl_change+0xddd/0x7da0 tc_new_tfilter+0xaa7/0x2420 rtnetlink_rcv_msg+0x95e/0xe90 which belongs to the cache kmalloc-2k of size 2048 Protect qdisc_rtab_list and the refcount with a dedicated spinlock. The (sleeping, GFP_KERNEL) allocation in qdisc_get_rtab() is performed before taking the lock; if a concurrent inserter added an identical table in the meantime the freshly allocated one is freed under the lock, so no duplicate is leaked. qdisc_put_rtab() now decrements the refcount and unlinks under the same lock. Fixes: 470502de5bdb ("net: sched: unlock rules update API") Suggested-by: Eric Dumazet Signed-off-by: Aldo Ariel Panzardo Cc: stable@vger.kernel.org Acked-by: Jamal Hadi Salim Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260715114114.446841-1-qwe.aldo@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sched/sch_api.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index b20dc987b907f..227e970603d47 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -413,12 +413,13 @@ static __u8 __detect_linklayer(struct tc_ratespec *r, __u32 *rtab) } static struct qdisc_rate_table *qdisc_rtab_list; +static DEFINE_SPINLOCK(qdisc_rtab_lock); struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, struct nlattr *tab, struct netlink_ext_ack *extack) { - struct qdisc_rate_table *rtab; + struct qdisc_rate_table *rtab, *new_rtab; if (tab == NULL || r->rate == 0 || r->cell_log == 0 || r->cell_log >= 32 || @@ -427,15 +428,20 @@ struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, return NULL; } + new_rtab = kmalloc(sizeof(*new_rtab), GFP_KERNEL); + + spin_lock(&qdisc_rtab_lock); for (rtab = qdisc_rtab_list; rtab; rtab = rtab->next) { if (!memcmp(&rtab->rate, r, sizeof(struct tc_ratespec)) && !memcmp(&rtab->data, nla_data(tab), 1024)) { rtab->refcnt++; + spin_unlock(&qdisc_rtab_lock); + kfree(new_rtab); return rtab; } } - rtab = kmalloc(sizeof(*rtab), GFP_KERNEL); + rtab = new_rtab; if (rtab) { rtab->rate = *r; rtab->refcnt = 1; @@ -447,6 +453,7 @@ struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, } else { NL_SET_ERR_MSG(extack, "Failed to allocate new qdisc rate table"); } + spin_unlock(&qdisc_rtab_lock); return rtab; } EXPORT_SYMBOL(qdisc_get_rtab); @@ -455,18 +462,25 @@ void qdisc_put_rtab(struct qdisc_rate_table *tab) { struct qdisc_rate_table *rtab, **rtabp; - if (!tab || --tab->refcnt) + if (!tab) return; + spin_lock(&qdisc_rtab_lock); + if (--tab->refcnt) { + spin_unlock(&qdisc_rtab_lock); + return; + } + for (rtabp = &qdisc_rtab_list; (rtab = *rtabp) != NULL; rtabp = &rtab->next) { if (rtab == tab) { *rtabp = rtab->next; - kfree(rtab); - return; + break; } } + spin_unlock(&qdisc_rtab_lock); + kfree(tab); } EXPORT_SYMBOL(qdisc_put_rtab); -- 2.53.0