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 C264A1E2834 for ; Sat, 8 Aug 2026 15:49:11 +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=1786204153; cv=none; b=CLjtoBRzGGM8IwdQqFxEi4WQuzLiAgPW2YwJ0OPmg4bHR5HAIZcnKfiP9Dp0FMBmbqrCJRjpQMbGJaDNybgxyx2I520oBT2zNWXHBjSj4u5WCMUDM1UofSGlkUVT97X+lsoTMhQ6L9SU3jRQAgXtCqLarSVYhtIy1/cY2hFiKt4= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786204153; c=relaxed/simple; bh=OPWxdWZugI16rpsC8rrbSpeIh0Kc26/PjillDsWQd2U=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OqV7XJcM3HIQd5qIS/iIzIqbWPX4dU9uMm8642Ml4mgFY6MggukwqdG0QiSQndQRZ/dr9SakEFN8v5vR0rhaaBSYQoyowMJKxPb4GuRnErn3ypGNKcEDj2qTe1EgFnP+mWvniihstNeUqGveZlWFyT8cKwZmSxg3dco5HClK99Y= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=QXXzGxLj; 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="QXXzGxLj" Received: by smtp.kernel.org (Postfix) with ESMTPSA id C4E461F000E9; Sat, 8 Aug 2026 15:49:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786204151; bh=tXRPF5pEJLpVtwteFDvh4ggw+AvRvR74EWqlDuPH/6c=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=QXXzGxLjZIffYZdkISAQzx/kYgu3Vb6Pyal05VHLtfR0nepFdoRqNmnFyB+K5obNk 9rwvfLP9ja+uSl9Th2/1DSh7s0dMsHVi61PzqddEi1yZe+2Ije84Dt0oloo1IiyYWi LZQYVTlq2bH39wy+Q/HK68I362FfVVg920bzqtdPd/BwYr6YpmRz8s5gBeeCPZH+VM ltYrLEF9hH8qH1D9EFlg61ASc7uQZbdmu4I/ZNwMZXpPE8Np3Vc9UVo0cqrzAbi9Ph eLHvEd1ZiKDmcsQPJpfLzrNwwI9cdaiUib1OOrZMLTlVYyLJjeOBj9knoLt29BhN73 MSP3j6IdGK7Jw== From: Sasha Levin To: stable@vger.kernel.org Cc: Aldo Ariel Panzardo , Eric Dumazet , Jamal Hadi Salim , Jakub Kicinski , Sasha Levin Subject: [PATCH 6.1.y] net/sched: serialize qdisc_rtab_list against concurrent get/put Date: Sat, 8 Aug 2026 11:49:09 -0400 Message-ID: <20260808154909.370825-1-sashal@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <2026072926-deflected-unvaried-0a4a@gregkh> References: <2026072926-deflected-unvaried-0a4a@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 f3e7067f9bac2..e979a62a45b69 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -412,12 +412,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 || @@ -426,15 +427,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; @@ -446,6 +452,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); @@ -454,18 +461,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