From nobody Sat Jul 25 20:48:45 2026 Received: from galois.linutronix.de (Galois.linutronix.de [193.142.43.55]) (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 99A2F29ACC5; Mon, 13 Jul 2026 21:24:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=193.142.43.55 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783977879; cv=none; b=KFToibX3NQnVUY75NZFUMoqBASERsiGDXdkciuJY1ii1KyyVu0MoAQ7LfsxVitmnrfLyPOTKvCJbgtBryZZ6I5WPJ1FTuEGFPk/oAmaAQwNDPWeQadFZwO2tUN2/iXCAYwb+TCeTlshI1fUjMmooQAwsif5lDD/QMCgepcS48RI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783977879; c=relaxed/simple; bh=N32DNEFO2/MLaLnVcvqXj2PifP1jyst6NzS7EyD9+bQ=; h=Date:From:To:Subject:Cc:In-Reply-To:References:MIME-Version: Message-ID:Content-Type; b=DrETDyX/o99SDh2OkOLWY2QMm4Ntwhx9nZJiICrJKE2srwSMyHMkxE4mcJ0+Zpt35Td7Al+Bokt1uaC2dxgLDqNV7WT73LIza/pOVByNdWK8/neyVzUwky59wgxrWxVwSUZwyyY/RLCPLwTVEQB3UULEOFB4iDNr1pgTHAmt/38= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de; spf=pass smtp.mailfrom=linutronix.de; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=kTs4Gysc; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=ihEvjjCv; arc=none smtp.client-ip=193.142.43.55 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linutronix.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="kTs4Gysc"; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="ihEvjjCv" Date: Mon, 13 Jul 2026 21:24:33 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020; t=1783977875; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Fy1Nwsl7MUMU/eRarrJ57PzOco6WbCRpEJiB/nbkhJU=; b=kTs4GyscSw7vi6aB4eHl3/92cSO8iMSbd2txCx0L4w73z9wl8Lo8pQ34C7UYy/16bjB1wK D2t+G03IcHHq3vvQgN6bh9cLINSrKJ63/GR5K2fYnf3ISzWiYJl2MTBTgu7/M+pu+yxKUg gdegG+91uKVRNCw/9ykmdfSj5rvSXw/lMTno4nIP0yTfbn0YEl2FbgYcHfL0y9x9jZX2Fj Enwd7YmoTvuPfSFLQL2W+e/mm5WLspoBLC/sch4TFz4rVY/vJJJEjeSa61GxtAd/giKTLF KORzQGwZht6eXONRbFxxsISFWuGaIK7evCrWI+sED1gdDzgezgEGKmxf1VLQIQ== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020e; t=1783977875; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Fy1Nwsl7MUMU/eRarrJ57PzOco6WbCRpEJiB/nbkhJU=; b=ihEvjjCvEzn5zG11smTaVDiOxDinwiFfHdcRy2gNInpCCo5JBsqkENVKuIlxXYLuLHBnBo 3PVJxUxr+v45yGCg== From: "tip-bot2 for Reinette Chatre" Sender: tip-bot2@linutronix.de Reply-to: linux-kernel@vger.kernel.org To: linux-tip-commits@vger.kernel.org Subject: [tip: x86/cache] fs/resctrl: Fix UAF from worker threads when domains are removed Cc: Sashiko , Tony Luck , Reinette Chatre , "Borislav Petkov (AMD)" , x86@kernel.org, linux-kernel@vger.kernel.org In-Reply-To: =?utf-8?q?=3C3f0e0752deb3421606dfc4600f0ab3a4ae098cd7=2E1783963?= =?utf-8?q?505=2Egit=2Ereinette=2Echatre=40intel=2Ecom=3E?= References: =?utf-8?q?=3C3f0e0752deb3421606dfc4600f0ab3a4ae098cd7=2E17839635?= =?utf-8?q?05=2Egit=2Ereinette=2Echatre=40intel=2Ecom=3E?= Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-ID: <178397787365.1844600.17123981908861028800.tip-bot2@tip-bot2> Robot-ID: Robot-Unsubscribe: Contact to get blacklisted from these emails Precedence: bulk Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The following commit has been merged into the x86/cache branch of tip: Commit-ID: 2566b5cd6a275c124e8f154fef6e815f92ec8d5c Gitweb: https://git.kernel.org/tip/2566b5cd6a275c124e8f154fef6e815f9= 2ec8d5c Author: Reinette Chatre AuthorDate: Mon, 13 Jul 2026 10:39:43 -07:00 Committer: Borislav Petkov (AMD) CommitterDate: Mon, 13 Jul 2026 14:09:38 -07:00 fs/resctrl: Fix UAF from worker threads when domains are removed The mbm_handle_overflow() and cqm_handle_limbo() workers read event counters and may sleep while doing so. They are scheduled via delayed_work embedded = in struct rdt_l3_mon_domain. Architecture allocates and frees these domains fr= om CPU hotplug callbacks under cpus_write_lock(), and the workers acquire cpus_read_lock() to keep the domain alive across their access. A use-after-free can occur when a worker is blocked waiting for cpus_read_lock() while the hotplug core holds cpus_write_lock(): the architecture frees the rdt_l3_mon_domain that contains the worker's work_struct. When the worker unblocks, the container_of() it performs on the embedded work pointer dereferences freed memory. Drop cpus_read_lock() from the workers and instead drain pending and in-fli= ght work synchronously before the architecture can free the domain. Since architecture offlines the domain under cpus_write_lock() after it has been unlinked from the RCU list and a grace period has elapsed, no new work can = be scheduled. The cancel only needs to wait out existing work. Drop rdtgroup_mutex during CPU offline around cancel_delayed_work_sync() so that a worker waiting on the mutex can complete before re-pinning the work on a different CPU. When offlining a CPU the architecture may iterate over resources in any ord= er. For example, the MBA control domain may be offlined before or after a corresponding L3 monitor domain. Ensure that resctrl fs cancels the worke= rs no matter what order the architecture offlines the domains. Fixes: 24247aeeabe9 ("x86/intel_rdt/cqm: Improve limbo list processing") Closes: https://sashiko.dev/#/patchset/20260429184858.36423-1-tony.luck%40i= ntel.com # [1] Reported-by: Sashiko Co-developed-by: Tony Luck Signed-off-by: Tony Luck Signed-off-by: Reinette Chatre Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/3f0e0752deb3421606dfc4600f0ab3a4ae098cd7.178= 3963505.git.reinette.chatre@intel.com --- fs/resctrl/monitor.c | 60 +++++++++++++++++++++++++++++++++++------- fs/resctrl/rdtgroup.c | 52 ++++++++++++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 15 deletions(-) diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c index 15e3eed..7340b1d 100644 --- a/fs/resctrl/monitor.c +++ b/fs/resctrl/monitor.c @@ -633,14 +633,22 @@ void mon_event_count(void *info) rr->err =3D 0; } =20 -static struct rdt_ctrl_domain *get_ctrl_domain_from_cpu(int cpu, - struct rdt_resource *r) +/* + * Find the software controller's ctrl domain that contains @cpu on resour= ce @r. + * + * Only called from the mbm_over worker via update_mba_bw() where the retu= rned + * domain is kept alive by cancel_delayed_work_sync() in + * resctrl_offline_ctrl_domain(). This drains this worker and then waits on + * rdtgroup_mutex held here before the architecture can free the ctrl doma= in. + * + * Context: Call from RCU read-side critical section. + */ +static struct rdt_ctrl_domain *get_sc_ctrl_domain_from_cpu(int cpu, + struct rdt_resource *r) { struct rdt_ctrl_domain *d; =20 - lockdep_assert_cpus_held(); - - list_for_each_entry(d, &r->ctrl_domains, hdr.list) { + list_for_each_entry_rcu(d, &r->ctrl_domains, hdr.list) { /* Find the domain that contains this CPU */ if (cpumask_test_cpu(cpu, &d->hdr.cpu_mask)) return d; @@ -701,7 +709,8 @@ static void update_mba_bw(struct rdtgroup *rgrp, struct= rdt_l3_mon_domain *dom_m if (WARN_ON_ONCE(!pmbm_data)) return; =20 - dom_mba =3D get_ctrl_domain_from_cpu(smp_processor_id(), r_mba); + guard(rcu)(); + dom_mba =3D get_sc_ctrl_domain_from_cpu(smp_processor_id(), r_mba); if (!dom_mba) { pr_warn_once("Failure to get domain for MBA update\n"); return; @@ -804,11 +813,25 @@ void cqm_handle_limbo(struct work_struct *work) unsigned long delay =3D msecs_to_jiffies(CQM_LIMBOCHECK_INTERVAL); struct rdt_l3_mon_domain *d; =20 - cpus_read_lock(); + /* + * Safe to run without CPU hotplug lock. Work is guaranteed to be + * canceled before the domain structure is removed. + */ mutex_lock(&rdtgroup_mutex); =20 + /* + * Ensure the worker is dedicated to a CPU as intended and not + * relocated by workqueue subsystem as part of CPU going offline. + */ + if (!is_percpu_thread()) + goto out_unlock; + d =3D container_of(work, struct rdt_l3_mon_domain, cqm_limbo.work); =20 + /* Domain is going offline */ + if (cpumask_empty(&d->hdr.cpu_mask)) + goto out_unlock; + __check_limbo(d, false); =20 if (has_busy_rmid(d)) { @@ -818,8 +841,8 @@ void cqm_handle_limbo(struct work_struct *work) delay); } =20 +out_unlock: mutex_unlock(&rdtgroup_mutex); - cpus_read_unlock(); } =20 /** @@ -851,7 +874,10 @@ void mbm_handle_overflow(struct work_struct *work) struct list_head *head; struct rdt_resource *r; =20 - cpus_read_lock(); + /* + * Safe to run without CPU hotplug lock. Work is guaranteed to be + * canceled before the domain structure is removed. + */ mutex_lock(&rdtgroup_mutex); =20 /* @@ -861,9 +887,24 @@ void mbm_handle_overflow(struct work_struct *work) if (!resctrl_mounted || !resctrl_arch_mon_capable()) goto out_unlock; =20 + /* + * Ensure the worker is dedicated to a CPU and not relocated by + * workqueue subsystem as part of CPU going offline since reading + * events depend on smp_processor_id(). After passing this check + * smp_processor_id() is valid for entire duration of this worker + * since it runs with rdtgroup_mutex held and the offline handler needs + * rdtgroup_mutex to offline the CPU being run on here. + */ + if (!is_percpu_thread()) + goto out_unlock; + r =3D resctrl_arch_get_resource(RDT_RESOURCE_L3); d =3D container_of(work, struct rdt_l3_mon_domain, mbm_over.work); =20 + /* Domain is going offline */ + if (cpumask_empty(&d->hdr.cpu_mask)) + goto out_unlock; + list_for_each_entry(prgrp, &rdt_all_groups, rdtgroup_list) { mbm_update(r, d, prgrp); =20 @@ -885,7 +926,6 @@ void mbm_handle_overflow(struct work_struct *work) =20 out_unlock: mutex_unlock(&rdtgroup_mutex); - cpus_read_unlock(); } =20 /** diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c index 2fd6873..30df902 100644 --- a/fs/resctrl/rdtgroup.c +++ b/fs/resctrl/rdtgroup.c @@ -4489,6 +4489,29 @@ static void domain_destroy_l3_mon_state(struct rdt_l= 3_mon_domain *d) =20 void resctrl_offline_ctrl_domain(struct rdt_resource *r, struct rdt_ctrl_d= omain *d) { + /* + * mbm_handle_overflow() may dereference this ctrl domain via + * update_mba_bw()->get_sc_ctrl_domain_from_cpu(). The architecture has + * unlinked the domain from the RCU list and waited a grace period, so + * no new worker iteration can find it; drain any worker that already + * holds a pointer to it before the architecture frees the domain. + * + * Software controller is enabled/disabled on mount/unmount with + * cpus_read_lock() held. Running here with cpus_write_lock() so + * there are no concurrent changes to software controller status. + */ + if (r->rid =3D=3D RDT_RESOURCE_MBA && is_mba_sc(r)) { + struct rdt_resource *l3 =3D resctrl_arch_get_resource(RDT_RESOURCE_L3); + struct rdt_l3_mon_domain *mon_d; + + list_for_each_entry_rcu(mon_d, &l3->mon_domains, hdr.list, lockdep_is_cp= us_held()) { + if (mon_d->hdr.id =3D=3D d->hdr.id) { + cancel_delayed_work_sync(&mon_d->mbm_over); + break; + } + } + } + mutex_lock(&rdtgroup_mutex); =20 if (supports_mba_mbps() && r->rid =3D=3D RDT_RESOURCE_MBA) @@ -4501,6 +4524,24 @@ void resctrl_offline_mon_domain(struct rdt_resource = *r, struct rdt_domain_hdr *h { struct rdt_l3_mon_domain *d; =20 + /* + * Called by architecture under CPU hotplug lock as it prepares to remove + * the domain which is guaranteed to be accessible here. + * The domain has been unlinked from the RCU list and a grace period + * has elapsed, so no new worker can be scheduled. Drain any worker that + * is in flight or pending before letting architecture proceed to free + * the domain that has the workers' struct delayed_work embedded. + * Do so before taking rdtgroup_mutex since the workers also acquire it. + */ + if (r->rid =3D=3D RDT_RESOURCE_L3 && + domain_header_is_valid(hdr, RESCTRL_MON_DOMAIN, RDT_RESOURCE_L3)) { + d =3D container_of(hdr, struct rdt_l3_mon_domain, hdr); + if (resctrl_is_mbm_enabled()) + cancel_delayed_work_sync(&d->mbm_over); + if (resctrl_is_mon_event_enabled(QOS_L3_OCCUP_EVENT_ID)) + cancel_delayed_work_sync(&d->cqm_limbo); + } + mutex_lock(&rdtgroup_mutex); =20 /* @@ -4517,8 +4558,6 @@ void resctrl_offline_mon_domain(struct rdt_resource *= r, struct rdt_domain_hdr *h goto out_unlock; =20 d =3D container_of(hdr, struct rdt_l3_mon_domain, hdr); - if (resctrl_is_mbm_enabled()) - cancel_delayed_work(&d->mbm_over); if (resctrl_is_mon_event_enabled(QOS_L3_OCCUP_EVENT_ID) && has_busy_rmid(= d)) { /* * When a package is going down, forcefully @@ -4529,7 +4568,6 @@ void resctrl_offline_mon_domain(struct rdt_resource *= r, struct rdt_domain_hdr *h * package never comes back. */ __check_limbo(d, true); - cancel_delayed_work(&d->cqm_limbo); } =20 domain_destroy_l3_mon_state(d); @@ -4710,12 +4748,16 @@ void resctrl_offline_cpu(unsigned int cpu) d =3D get_mon_domain_from_cpu(cpu, l3); if (d) { if (resctrl_is_mbm_enabled() && cpu =3D=3D d->mbm_work_cpu) { - cancel_delayed_work(&d->mbm_over); + mutex_unlock(&rdtgroup_mutex); + cancel_delayed_work_sync(&d->mbm_over); + mutex_lock(&rdtgroup_mutex); mbm_setup_overflow_handler(d, 0, cpu); } if (resctrl_is_mon_event_enabled(QOS_L3_OCCUP_EVENT_ID) && cpu =3D=3D d->cqm_work_cpu && has_busy_rmid(d)) { - cancel_delayed_work(&d->cqm_limbo); + mutex_unlock(&rdtgroup_mutex); + cancel_delayed_work_sync(&d->cqm_limbo); + mutex_lock(&rdtgroup_mutex); cqm_setup_limbo_handler(d, 0, cpu); } }