* [PATCH 0/2] sched/cache: Fix use after free mm access in account_mm_sched()
@ 2026-09-02 0:08 Tim Chen
2026-09-02 0:08 ` [PATCH 1/2] sched/cache: Decouple sched_cache_group from mm Tim Chen
2026-09-02 0:08 ` [PATCH 2/2] sched/cache: Introduce task_struct->sched_cache_grp Tim Chen
0 siblings, 2 replies; 3+ messages in thread
From: Tim Chen @ 2026-09-02 0:08 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar
Cc: Tim Chen, Chen Yu, Hyunwoo Kim, Kees Cook, Christian Brauner,
Alexander Viro, Jan Kara, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Shrikanth Hegde, Qais Yousef,
Aaron Lu, Srikar Dronamraju, Vineeth Remanan Pillai, linux-kernel,
linux-mm, chen . yu @ linux . dev
Hyunwoo Kim reported a KASAN use-after-free in account_mm_sched():
https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/
Cache-aware scheduling keeps its per-address-space statistics inside the
mm:
struct mm_struct {
...
struct sched_cache_stat sc_stat;
};
so their lifetime is the mm's lifetime. mm_alloc_sched() allocates
sc_stat.pcpu_sched from mm_init(), and mm_destroy_sched() frees it from
__mmdrop().
The scheduler, though, reaches that object from contexts that hold no
reference on the mm:
- account_mm_sched() runs from update_curr() holding only the rq lock,
and dereferences rq->curr->mm.
- the load-balance predicates - can_migrate_llc_task() ->
invalid_llc_nr() / exceed_llc_capacity() - and the task_cache_work()
LLC occupancy scan read p->mm of *remote* tasks.
Nothing on those paths keeps the mm alive, so an exit, or an exec_mmap()
installing a new mm, can free pcpu_sched underneath a concurrent reader.
Serializing the two sides - taking the rq lock in the mm teardown path -
would put a scheduler lock in the middle of __mmdrop(), which is a lot
of coupling to pay for a statistics object. Give the object its own
lifetime instead.
Patch 1 lifts sched_cache_stat out of mm_struct, renames it
sched_cache_group, and turns it into a refcounted object freed via
call_rcu(); the mm now merely points at it.
Patch 2 gives every task its own reference in
task_struct->sched_cache_grp - taken in copy_mm() and exec_mmap(),
dropped in exit_mm() - and converts the scheduler to read
p->sched_cache_grp rather than p->mm->sc_stat. Readers access
the sched_cache_grp without worry that it was freed as it
has a ref count on the object.
The two patches are one fix. Patch 1 does not stand alone, so they need
to be applied, and backported, as a pair.
A welcome side effect of the decoupling is that the group is no longer
welded to an address space, so a later series can key it on a cgroup, a
core-scheduling cookie or a numa_group instead of on a single mm.
These are the first two patches of the cache-aware prctl RFC series
https://lore.kernel.org/lkml/cover.1787955777.git.tim.c.chen@linux.intel.com/
reposted on their own with the changelogs rewritten and minor updates around the
use-after-free, so that they can be considered ahead of the rest of that
series. Hyunwoo confirmed the splat is gone; his Tested-by is on both
patches.
Tim
---
Tim Chen (2):
sched/cache: Decouple sched_cache_group from mm
sched/cache: Introduce task_struct->sched_cache_grp
fs/exec.c | 14 +++
include/linux/mm_types.h | 15 +--
include/linux/sched.h | 11 +-
kernel/exit.c | 28 ++++--
kernel/fork.c | 23 +++++
kernel/sched/build_utility.c | 4 +
kernel/sched/cache_sched.c | 39 ++++++++
kernel/sched/fair.c | 188 ++++++++++++++++++++++-------------
kernel/sched/sched.h | 3 +
9 files changed, 239 insertions(+), 86 deletions(-)
create mode 100644 kernel/sched/cache_sched.c
--
2.32.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 1/2] sched/cache: Decouple sched_cache_group from mm
2026-09-02 0:08 [PATCH 0/2] sched/cache: Fix use after free mm access in account_mm_sched() Tim Chen
@ 2026-09-02 0:08 ` Tim Chen
2026-09-02 0:08 ` [PATCH 2/2] sched/cache: Introduce task_struct->sched_cache_grp Tim Chen
1 sibling, 0 replies; 3+ messages in thread
From: Tim Chen @ 2026-09-02 0:08 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar
Cc: Tim Chen, Chen Yu, Hyunwoo Kim, Kees Cook, Christian Brauner,
Alexander Viro, Jan Kara, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Shrikanth Hegde, Qais Yousef,
Aaron Lu, Srikar Dronamraju, Vineeth Remanan Pillai, linux-kernel,
linux-mm, chen . yu @ linux . dev
Currently the sched cache grouping is by mm and the scheduling statistics
sched_cache_stat lives in the mm structure. This ties the life cycle
of scheduling stats with mm.
In account_mm_sched(), the scheduling stats are accessed by
task->mm->sc_stat. However, a task may be switching mm on one CPU when
another CPU is running account_mm_sched(), and possibly accessing the
old mm that was freed. This problem was found when running tests with
KASAN by Hyunwoo. https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/
Instead of serializing the mm access by introducing extra acquisition of
rq lock in the mm free path, extract sched_cache_stat from mm_struct,
rename it as sched_cache_group and manage its life cycle apart from
mm_struct with its own ref counting. Access sched_cache_group directly
from a task instead of having to go through a task's mm. This allows us
to later add a refcount on sched_cache_group when a task links to it,
preventing the use after free issue when accessing stale and released
old mm and its sched cache stat a task switches to a new mm while
account_mm_sched() is done elsewhere.
The other benefit of this restructure is in the future, the grouping of
tasks to a LLC would have the flexibility to be associated with cgroup,
cookie group, numa_group or others instead of just with a single mm
address space.
Rename sched_cache_stat to sched_cache_group and turn it into a refcounted
object allocated from mm_struct. The mm_struct now holds a pointer
(sched_cache_grp) to this object instead of embedding it.
Introduce kernel/sched/cache_sched.c to host the cache aware scheduling
helpers and define sched_cache_group_put() there.
Meanwhile skip kthreads in account_mm_sched(), consistent with
task_tick_cache().
Reported-by: Hyunwoo Kim <imv4bel@gmail.com>
Closes: https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/
Tested-by: Hyunwoo Kim <imv4bel@gmail.com>
Fixes: df0d98475954 ("sched/cache: Introduce infrastructure for cache-aware load balancing")
Co-developed-by: Chen Yu <yu.c.chen@intel.com>
Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Signed-off-by: Tim Chen <tim.c.chen@linux.intel.com>
---
include/linux/mm_types.h | 15 ++---
include/linux/sched.h | 8 ++-
kernel/exit.c | 6 +-
kernel/sched/build_utility.c | 4 ++
kernel/sched/cache_sched.c | 20 ++++++
kernel/sched/fair.c | 118 ++++++++++++++++++++++-------------
6 files changed, 113 insertions(+), 58 deletions(-)
create mode 100644 kernel/sched/cache_sched.c
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 6d815f6440c9..f3e5a2fadbe5 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -1226,7 +1226,7 @@ struct mm_struct {
struct mm_mm_cid mm_cid;
/* sched_cache related statistics */
- struct sched_cache_stat sc_stat;
+ struct sched_cache_group *sched_cache_grp;
#ifdef CONFIG_MMU
atomic_long_t pgtables_bytes; /* size of all page tables */
#endif
@@ -1624,8 +1624,9 @@ static inline unsigned int mm_cid_size(void)
#endif /* CONFIG_SCHED_MM_CID */
#ifdef CONFIG_SCHED_CACHE
-void mm_init_sched(struct mm_struct *mm,
- struct sched_cache_time __percpu *pcpu_sched);
+int mm_init_sched(struct mm_struct *mm,
+ struct sched_cache_time __percpu *pcpu_sched);
+void mm_destroy_sched(struct mm_struct *mm);
static inline int mm_alloc_sched_noprof(struct mm_struct *mm)
{
@@ -1635,17 +1636,11 @@ static inline int mm_alloc_sched_noprof(struct mm_struct *mm)
if (!pcpu_sched)
return -ENOMEM;
- mm_init_sched(mm, pcpu_sched);
- return 0;
+ return mm_init_sched(mm, pcpu_sched);
}
#define mm_alloc_sched(...) alloc_hooks(mm_alloc_sched_noprof(__VA_ARGS__))
-static inline void mm_destroy_sched(struct mm_struct *mm)
-{
- free_percpu(mm->sc_stat.pcpu_sched);
- mm->sc_stat.pcpu_sched = NULL;
-}
#else /* !CONFIG_SCHED_CACHE */
static inline int mm_alloc_sched(struct mm_struct *mm) { return 0; }
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cc..1f254364f216 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -2405,7 +2405,7 @@ struct sched_cache_time {
unsigned long epoch;
};
-struct sched_cache_stat {
+struct sched_cache_group {
struct sched_cache_time __percpu *pcpu_sched;
raw_spinlock_t lock;
unsigned long epoch;
@@ -2413,11 +2413,15 @@ struct sched_cache_stat {
unsigned long next_scan;
unsigned long footprint;
int cpu;
+ refcount_t refcnt;
+ struct rcu_head rcu;
} ____cacheline_aligned_in_smp;
+void sched_cache_group_put(struct sched_cache_group *grp);
+
#else
-struct sched_cache_stat { };
+struct sched_cache_group { };
#endif
diff --git a/kernel/exit.c b/kernel/exit.c
index 97686af89501..006edcc0c2c5 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -560,12 +560,12 @@ static void exit_mm_sched_cache(struct mm_struct *mm)
return;
/*
* No lock protection due to performance considerations.
- * Make sure mm->sc_stat.footprint does not become
+ * Make sure the group footprint does not become
* negative.
*/
- fp = READ_ONCE(mm->sc_stat.footprint);
+ fp = READ_ONCE(mm->sched_cache_grp->footprint);
sub = min(fp, current->total_numa_faults);
- WRITE_ONCE(mm->sc_stat.footprint, fp - sub);
+ WRITE_ONCE(mm->sched_cache_grp->footprint, fp - sub);
}
#else
static inline void exit_mm_sched_cache(struct mm_struct *mm)
diff --git a/kernel/sched/build_utility.c b/kernel/sched/build_utility.c
index e2cf3b08d4e9..24202893b262 100644
--- a/kernel/sched/build_utility.c
+++ b/kernel/sched/build_utility.c
@@ -89,6 +89,10 @@
# include "core_sched.c"
#endif
+#ifdef CONFIG_SCHED_CACHE
+# include "cache_sched.c"
+#endif
+
#ifdef CONFIG_PSI
# include "psi.c"
#endif
diff --git a/kernel/sched/cache_sched.c b/kernel/sched/cache_sched.c
new file mode 100644
index 000000000000..d492df55f9d5
--- /dev/null
+++ b/kernel/sched/cache_sched.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include "sched.h"
+
+static void sched_cache_group_free_rcu(struct rcu_head *rcu)
+{
+ struct sched_cache_group *grp =
+ container_of(rcu, struct sched_cache_group, rcu);
+
+ /* free_percpu() may be called from atomic context. */
+ free_percpu(grp->pcpu_sched);
+ kfree(grp);
+}
+
+void sched_cache_group_put(struct sched_cache_group *grp)
+{
+ if (!grp || !refcount_dec_and_test(&grp->refcnt))
+ return;
+
+ call_rcu(&grp->rcu, sched_cache_group_free_rcu);
+}
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8dff37059faf..8587dcbaa1cf 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -1489,7 +1489,7 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu)
* excluded.
*/
llc = sd->llc_bytes;
- footprint = READ_ONCE(mm->sc_stat.footprint);
+ footprint = READ_ONCE(mm->sched_cache_grp->footprint);
/*
* Scale the LLC size by 256*llc_aggr_tolerance
@@ -1534,7 +1534,7 @@ static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p,
if (scale == INT_MAX)
return false;
- return !fits_capacity((mm->sc_stat.nr_running_avg * cpu_smt_num_threads),
+ return !fits_capacity((mm->sched_cache_grp->nr_running_avg * cpu_smt_num_threads),
(scale * per_cpu(sd_llc_size, cpu)));
}
@@ -1611,12 +1611,20 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p)
}
}
-void mm_init_sched(struct mm_struct *mm,
- struct sched_cache_time __percpu *_pcpu_sched)
+int mm_init_sched(struct mm_struct *mm,
+ struct sched_cache_time __percpu *_pcpu_sched)
{
+ struct sched_cache_group *grp;
unsigned long epoch = 0;
int i;
+ grp = kzalloc_obj(*grp);
+ if (!grp) {
+ free_percpu(_pcpu_sched);
+ mm->sched_cache_grp = NULL;
+ return -ENOMEM;
+ }
+
for_each_possible_cpu(i) {
struct sched_cache_time *pcpu_sched = per_cpu_ptr(_pcpu_sched, i);
struct rq *rq = cpu_rq(i);
@@ -1627,18 +1635,35 @@ void mm_init_sched(struct mm_struct *mm,
epoch = rq->cpu_epoch;
}
- raw_spin_lock_init(&mm->sc_stat.lock);
- mm->sc_stat.epoch = epoch;
- mm->sc_stat.cpu = -1;
- mm->sc_stat.next_scan = jiffies;
- mm->sc_stat.nr_running_avg = 0;
- mm->sc_stat.footprint = 0;
+ raw_spin_lock_init(&grp->lock);
+ grp->epoch = epoch;
+ grp->cpu = -1;
+ grp->next_scan = jiffies;
+ grp->nr_running_avg = 0;
+ grp->footprint = 0;
+ refcount_set(&grp->refcnt, 1);
/*
- * The update to mm->sc_stat should not be reordered
- * before initialization to mm's other fields, in case
+ * The update to grp->pcpu_sched should not be reordered
+ * before initialization to grp's other fields, in case
* the readers may get invalid mm_sched_epoch, etc.
*/
- smp_store_release(&mm->sc_stat.pcpu_sched, _pcpu_sched);
+ smp_store_release(&grp->pcpu_sched, _pcpu_sched);
+ /*
+ * Publish the group last. Not every reader qualifies it by
+ * grp->pcpu_sched - can_migrate_llc_task() only checks that the
+ * pointer is non-NULL before reading grp->footprint and
+ * grp->nr_running_avg - so a reachable group must already be
+ * fully initialized.
+ */
+ mm->sched_cache_grp = grp;
+ return 0;
+}
+
+void mm_destroy_sched(struct mm_struct *mm)
+{
+ if (mm->sched_cache_grp)
+ sched_cache_group_put(mm->sched_cache_grp);
+ mm->sched_cache_grp = NULL;
}
/* because why would C be fully specified */
@@ -1696,7 +1721,7 @@ static int get_pref_llc(struct task_struct *p, struct mm_struct *mm)
if (!mm)
return -1;
- mm_sched_cpu = READ_ONCE(mm->sc_stat.cpu);
+ mm_sched_cpu = READ_ONCE(mm->sched_cache_grp->cpu);
if (mm_sched_cpu != -1) {
mm_sched_llc = llc_id(mm_sched_cpu);
@@ -1739,11 +1764,15 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
/*
* init_task, kthreads and user thread created
* by user_mode_thread() don't have mm.
+ *
+ * A kthread can temporarily adopt an mm via kthread_use_mm(),
+ * so p->mm alone does not imply a user task.
*/
- if (!mm || !mm->sc_stat.pcpu_sched)
+ if (!mm || p->flags & PF_KTHREAD || !mm->sched_cache_grp ||
+ !mm->sched_cache_grp->pcpu_sched)
return;
- pcpu_sched = per_cpu_ptr(mm->sc_stat.pcpu_sched, cpu_of(rq));
+ pcpu_sched = per_cpu_ptr(mm->sched_cache_grp->pcpu_sched, cpu_of(rq));
scoped_guard (raw_spinlock, &rq->cpu_epoch_lock) {
__update_mm_sched(rq, pcpu_sched);
@@ -1756,11 +1785,11 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
* If this process hasn't hit task_cache_work() for a while invalidate
* its preferred state.
*/
- if ((long)(epoch - READ_ONCE(mm->sc_stat.epoch)) > llc_epoch_affinity_timeout ||
+ if ((long)(epoch - READ_ONCE(mm->sched_cache_grp->epoch)) > llc_epoch_affinity_timeout ||
invalid_llc_nr(mm, p, cpu_of(rq)) ||
exceed_llc_capacity(mm, cpu_of(rq))) {
- if (READ_ONCE(mm->sc_stat.cpu) != -1)
- WRITE_ONCE(mm->sc_stat.cpu, -1);
+ if (READ_ONCE(mm->sched_cache_grp->cpu) != -1)
+ WRITE_ONCE(mm->sched_cache_grp->cpu, -1);
}
mm_sched_llc = get_pref_llc(p, mm);
@@ -1784,19 +1813,19 @@ static void task_tick_cache(struct rq *rq, struct task_struct *p)
return;
if (!mm || p->flags & PF_KTHREAD ||
- !mm->sc_stat.pcpu_sched)
+ !mm->sched_cache_grp->pcpu_sched)
return;
epoch = rq->cpu_epoch;
/* avoid moving backwards */
- if (time_after_eq(mm->sc_stat.epoch, epoch))
+ if (time_after_eq(mm->sched_cache_grp->epoch, epoch))
return;
- guard(raw_spinlock)(&mm->sc_stat.lock);
+ guard(raw_spinlock)(&mm->sched_cache_grp->lock);
if (work->next == work) {
task_work_add(p, work, TWA_RESUME);
- WRITE_ONCE(mm->sc_stat.epoch, epoch);
+ WRITE_ONCE(mm->sched_cache_grp->epoch, epoch);
}
}
@@ -1808,7 +1837,7 @@ static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p)
if (!static_branch_likely(&sched_numa_balancing))
goto out;
- cpu = READ_ONCE(p->mm->sc_stat.cpu);
+ cpu = READ_ONCE(p->mm->sched_cache_grp->cpu);
if (cpu != -1)
nid = cpu_to_node(cpu);
curr_cpu = task_cpu(p);
@@ -1880,12 +1909,12 @@ static void task_cache_work(struct callback_head *work)
if (p->flags & PF_EXITING)
return;
- next_scan = READ_ONCE(mm->sc_stat.next_scan);
+ next_scan = READ_ONCE(mm->sched_cache_grp->next_scan);
if (time_before(now, next_scan))
return;
/* only 1 thread is allowed to scan */
- if (!try_cmpxchg(&mm->sc_stat.next_scan, &next_scan,
+ if (!try_cmpxchg(&mm->sched_cache_grp->next_scan, &next_scan,
now + max_t(unsigned long,
READ_ONCE(llc_epoch_period), 1)))
return;
@@ -1893,8 +1922,8 @@ static void task_cache_work(struct callback_head *work)
curr_cpu = task_cpu(p);
if (invalid_llc_nr(mm, p, curr_cpu) ||
exceed_llc_capacity(mm, curr_cpu)) {
- if (READ_ONCE(mm->sc_stat.cpu) != -1)
- WRITE_ONCE(mm->sc_stat.cpu, -1);
+ if (READ_ONCE(mm->sched_cache_grp->cpu) != -1)
+ WRITE_ONCE(mm->sched_cache_grp->cpu, -1);
return;
}
@@ -1917,8 +1946,10 @@ static void task_cache_work(struct callback_head *work)
continue;
for_each_cpu(i, sched_domain_span(sd)) {
+ struct sched_cache_group *grp = mm->sched_cache_grp;
+
occ = fraction_mm_sched(cpu_rq(i),
- per_cpu_ptr(mm->sc_stat.pcpu_sched, i));
+ per_cpu_ptr(grp->pcpu_sched, i));
a_occ += occ;
if (occ > m_occ) {
m_occ = occ;
@@ -1951,7 +1982,7 @@ static void task_cache_work(struct callback_head *work)
m_a_cpu = m_cpu;
}
- if (llc_id(cpu) == llc_id(READ_ONCE(mm->sc_stat.cpu)))
+ if (llc_id(cpu) == llc_id(READ_ONCE(mm->sched_cache_grp->cpu)))
curr_m_a_occ = a_occ;
cpumask_andnot(cpus, cpus, sched_domain_span(sd));
@@ -1960,7 +1991,7 @@ static void task_cache_work(struct callback_head *work)
if (m_a_occ > (2 * curr_m_a_occ)) {
/*
- * Avoid switching sc_stat.cpu too fast.
+ * Avoid switching sched_cache_grp->cpu too fast.
* The reason to choose 2X is because:
* 1. It is better to keep the preferred LLC stable,
* rather than changing it frequently and cause migrations
@@ -1969,10 +2000,10 @@ static void task_cache_work(struct callback_head *work)
* 3. 2X is chosen based on test results, as it delivers
* the optimal performance gain so far.
*/
- WRITE_ONCE(mm->sc_stat.cpu, m_a_cpu);
+ WRITE_ONCE(mm->sched_cache_grp->cpu, m_a_cpu);
}
- update_avg_scale(&mm->sc_stat.nr_running_avg, nr_running);
+ update_avg_scale(&mm->sched_cache_grp->nr_running_avg, nr_running);
free_cpumask_var(cpus);
}
@@ -3776,18 +3807,19 @@ static void task_numa_placement(struct task_struct *p)
* heuristic and occasional lost updates are tolerable.
*
* If a task exits, its corresponding footprint must
- * be subtracted from the mm->sc_stat.footprint, otherwise
- * the mm->sc_stat.footprint will not converge:
- * the exiting thread's footprint remains unchanged/undecayed
- * in mm->sc_stat.footprint. See exit_mm().
+ * be subtracted from the mm->sched_cache_grp->footprint,
+ * otherwise the mm->sched_cache_grp->footprint will not
+ * converge: the exiting thread's footprint remains
+ * unchanged/undecayed in mm->sched_cache_grp->footprint.
+ * See exit_mm().
*
* Lost updates and unsynchronized subtraction
* in exit_mm() can cause footprint + diff to
* go negative. Clamp to zero to prevent the
* unsigned footprint from wrapping.
*/
- new_fp = (long)READ_ONCE(p->mm->sc_stat.footprint) + diff;
- WRITE_ONCE(p->mm->sc_stat.footprint,
+ new_fp = (long)READ_ONCE(p->mm->sched_cache_grp->footprint) + diff;
+ WRITE_ONCE(p->mm->sched_cache_grp->footprint,
max(new_fp, 0L));
#endif
}
@@ -10703,18 +10735,18 @@ static enum llc_mig can_migrate_llc_task(int src_cpu, int dst_cpu,
int cpu;
mm = p->mm;
- if (!mm)
+ if (!mm || !mm->sched_cache_grp)
return mig_unrestricted;
- cpu = READ_ONCE(mm->sc_stat.cpu);
+ cpu = READ_ONCE(mm->sched_cache_grp->cpu);
if (cpu < 0 || cpus_share_cache(src_cpu, dst_cpu))
return mig_unrestricted;
/* skip cache aware load balance for too many threads */
if (invalid_llc_nr(mm, p, dst_cpu) ||
exceed_llc_capacity(mm, dst_cpu)) {
- if (READ_ONCE(mm->sc_stat.cpu) != -1)
- WRITE_ONCE(mm->sc_stat.cpu, -1);
+ if (READ_ONCE(mm->sched_cache_grp->cpu) != -1)
+ WRITE_ONCE(mm->sched_cache_grp->cpu, -1);
return mig_unrestricted;
}
--
2.32.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH 2/2] sched/cache: Introduce task_struct->sched_cache_grp
2026-09-02 0:08 [PATCH 0/2] sched/cache: Fix use after free mm access in account_mm_sched() Tim Chen
2026-09-02 0:08 ` [PATCH 1/2] sched/cache: Decouple sched_cache_group from mm Tim Chen
@ 2026-09-02 0:08 ` Tim Chen
1 sibling, 0 replies; 3+ messages in thread
From: Tim Chen @ 2026-09-02 0:08 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar
Cc: Tim Chen, Chen Yu, Hyunwoo Kim, Kees Cook, Christian Brauner,
Alexander Viro, Jan Kara, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Shrikanth Hegde, Qais Yousef,
Aaron Lu, Srikar Dronamraju, Vineeth Remanan Pillai, linux-kernel,
linux-mm, chen . yu @ linux . dev
Add a sched_cache_grp pointer to task_struct so that scheduler code
can access the cache group directly via the task, without going
through mm->sched_cache_grp. This decouples the scheduler's hot-path
accesses from the mm_struct.
Each task holds its own refcount on the sched_cache_group, separate
from the reference held by its mm_struct. The reference is acquired
in copy_mm() (fork) and exec_mmap() (exec), and released in exit_mm().
This fixes use after free problem when accessing sched_cache_grp
in account_mm_sched() via mm as reported in
https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/
Convert all scheduler code in fair.c and exit.c to use
p->sched_cache_grp instead of p->mm->sched_cache_grp.
Add sched_cache_group_get() to kernel/sched/cache_sched.c.
Reported-by: Hyunwoo Kim <imv4bel@gmail.com>
Closes: https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/
Tested-by: Hyunwoo Kim <imv4bel@gmail.com>
Fixes: df0d98475954 ("sched/cache: Introduce infrastructure for cache-aware load balancing")
Co-developed-by: Chen Yu <yu.c.chen@intel.com>
Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Signed-off-by: Tim Chen <tim.c.chen@linux.intel.com>
---
fs/exec.c | 14 ++++
include/linux/sched.h | 3 +
kernel/exit.c | 26 +++++--
kernel/fork.c | 23 ++++++
kernel/sched/cache_sched.c | 19 +++++
kernel/sched/fair.c | 142 +++++++++++++++++++++----------------
kernel/sched/sched.h | 3 +
7 files changed, 164 insertions(+), 66 deletions(-)
diff --git a/fs/exec.c b/fs/exec.c
index 745f6eb5279e..7a8a9954343e 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -882,6 +882,20 @@ static int exec_mmap(struct linux_binprm *bprm)
active_mm = tsk->active_mm;
tsk->active_mm = mm;
tsk->mm = mm;
+#ifdef CONFIG_SCHED_CACHE
+ {
+ struct sched_cache_group *old_grp, *new_grp;
+
+ old_grp = rcu_dereference_protected(tsk->sched_cache_grp, true);
+
+ /* Acquire the reference before publishing the pointer. */
+ new_grp = sched_cache_group_get(mm->sched_cache_grp);
+
+ rcu_assign_pointer(tsk->sched_cache_grp, new_grp);
+ if (old_grp)
+ sched_cache_group_put(old_grp);
+ }
+#endif
mm_init_cid(mm, tsk);
exec_state = task_exec_state_replace(tsk, exec_state);
/*
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 1f254364f216..cab8e89b1462 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1434,6 +1434,7 @@ struct task_struct {
#ifdef CONFIG_SCHED_CACHE
struct callback_head cache_work;
int preferred_llc;
+ struct sched_cache_group __rcu *sched_cache_grp;
/* 1: task was enqueued to its preferred LLC, 0 otherwise */
int pref_llc_queued;
#endif
@@ -2418,6 +2419,8 @@ struct sched_cache_group {
} ____cacheline_aligned_in_smp;
void sched_cache_group_put(struct sched_cache_group *grp);
+struct sched_cache_group *sched_cache_group_get(struct sched_cache_group *grp);
+struct sched_cache_group *task_cache_group_get(struct task_struct *p);
#else
diff --git a/kernel/exit.c b/kernel/exit.c
index 006edcc0c2c5..442535778ce1 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -552,23 +552,25 @@ void mm_update_next_owner(struct mm_struct *mm)
* Subtract the memory footprint of the current task from
* mm.
*/
-static void exit_mm_sched_cache(struct mm_struct *mm)
+static void exit_mm_sched_cache(void)
{
+ struct sched_cache_group *grp =
+ rcu_dereference_protected(current->sched_cache_grp, true);
unsigned long fp, sub;
- if (!current->total_numa_faults)
+ if (!grp || !current->total_numa_faults)
return;
/*
* No lock protection due to performance considerations.
* Make sure the group footprint does not become
* negative.
*/
- fp = READ_ONCE(mm->sched_cache_grp->footprint);
+ fp = READ_ONCE(grp->footprint);
sub = min(fp, current->total_numa_faults);
- WRITE_ONCE(mm->sched_cache_grp->footprint, fp - sub);
+ WRITE_ONCE(grp->footprint, fp - sub);
}
#else
-static inline void exit_mm_sched_cache(struct mm_struct *mm)
+static inline void exit_mm_sched_cache(void)
{
}
#endif /* CONFIG_SCHED_CACHE CONFIG_NUMA_BALANCING */
@@ -585,7 +587,19 @@ static void exit_mm(void)
if (!mm)
return;
- exit_mm_sched_cache(mm);
+ exit_mm_sched_cache();
+
+#ifdef CONFIG_SCHED_CACHE
+ {
+ struct sched_cache_group *grp =
+ rcu_dereference_protected(current->sched_cache_grp, true);
+
+ rcu_assign_pointer(current->sched_cache_grp, NULL);
+
+ if (grp)
+ sched_cache_group_put(grp);
+ }
+#endif
mmap_read_lock(mm);
mmgrab_lazy_tlb(mm);
diff --git a/kernel/fork.c b/kernel/fork.c
index 416758c8a3d4..2e79548cb7c1 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1599,6 +1599,19 @@ static int copy_mm(u64 clone_flags, struct task_struct *tsk)
tsk->mm = mm;
tsk->active_mm = mm;
+#ifdef CONFIG_SCHED_CACHE
+ {
+ /*
+ * A task holds its own reference on the group, separate from
+ * the reference held by its mm_struct. Acquire it before
+ * publishing the pointer.
+ */
+ struct sched_cache_group *grp =
+ sched_cache_group_get(mm->sched_cache_grp);
+
+ rcu_assign_pointer(tsk->sched_cache_grp, grp);
+ }
+#endif
return 0;
}
@@ -2599,6 +2612,16 @@ __latent_entropy struct task_struct *copy_process(
bad_fork_cleanup_namespaces:
exit_nsproxy_namespaces(p);
bad_fork_cleanup_mm:
+#ifdef CONFIG_SCHED_CACHE
+ /*
+ * copy_mm() took a task reference on the cache group; a failed fork
+ * never reaches exit_mm(), so release it here to avoid leaking the
+ * group and its per-CPU buffer.
+ */
+ sched_cache_group_put(rcu_dereference_protected(p->sched_cache_grp, true));
+ RCU_INIT_POINTER(p->sched_cache_grp, NULL);
+#endif
+
if (p->mm) {
mm_clear_owner(p->mm, p);
mmput(p->mm);
diff --git a/kernel/sched/cache_sched.c b/kernel/sched/cache_sched.c
index d492df55f9d5..99d07e1e067c 100644
--- a/kernel/sched/cache_sched.c
+++ b/kernel/sched/cache_sched.c
@@ -1,6 +1,25 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "sched.h"
+struct sched_cache_group *sched_cache_group_get(struct sched_cache_group *grp)
+{
+ /*
+ * refcount_inc_not_zero() is the acquire primitive for lockless
+ * (RCU) lookups; plain refcount_inc() would scribble the count if
+ * it already reached zero. Return NULL in that case.
+ */
+ if (grp && !refcount_inc_not_zero(&grp->refcnt))
+ grp = NULL;
+
+ return grp;
+}
+
+struct sched_cache_group *task_cache_group_get(struct task_struct *p)
+{
+ guard(rcu)();
+ return sched_cache_group_get(rcu_dereference(p->sched_cache_grp));
+}
+
static void sched_cache_group_free_rcu(struct rcu_head *rcu)
{
struct sched_cache_group *grp =
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8587dcbaa1cf..4226af32728a 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -1470,7 +1470,7 @@ static inline int get_sched_cache_scale(int mul)
return (1 + (tol - 1) * mul);
}
-static bool exceed_llc_capacity(struct mm_struct *mm, int cpu)
+static bool exceed_llc_capacity(struct sched_cache_group *grp, int cpu)
{
#ifdef CONFIG_NUMA_BALANCING
unsigned long llc, footprint;
@@ -1489,7 +1489,7 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu)
* excluded.
*/
llc = sd->llc_bytes;
- footprint = READ_ONCE(mm->sched_cache_grp->footprint);
+ footprint = READ_ONCE(grp->footprint);
/*
* Scale the LLC size by 256*llc_aggr_tolerance
@@ -1518,7 +1518,7 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu)
return false;
}
-static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p,
+static bool invalid_llc_nr(struct sched_cache_group *grp, struct task_struct *p,
int cpu)
{
int scale;
@@ -1534,7 +1534,7 @@ static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p,
if (scale == INT_MAX)
return false;
- return !fits_capacity((mm->sched_cache_grp->nr_running_avg * cpu_smt_num_threads),
+ return !fits_capacity((grp->nr_running_avg * cpu_smt_num_threads),
(scale * per_cpu(sd_llc_size, cpu)));
}
@@ -1714,14 +1714,14 @@ static unsigned long fraction_mm_sched(struct rq *rq,
return div64_u64(NICE_0_LOAD * pcpu_sched->runtime, rq->cpu_runtime + 1);
}
-static int get_pref_llc(struct task_struct *p, struct mm_struct *mm)
+static int get_pref_llc(struct task_struct *p, struct sched_cache_group *grp)
{
int mm_sched_llc = -1, mm_sched_cpu;
- if (!mm)
+ if (!grp)
return -1;
- mm_sched_cpu = READ_ONCE(mm->sched_cache_grp->cpu);
+ mm_sched_cpu = READ_ONCE(grp->cpu);
if (mm_sched_cpu != -1) {
mm_sched_llc = llc_id(mm_sched_cpu);
@@ -1751,8 +1751,8 @@ static unsigned int task_running_on_cpu(int cpu, struct task_struct *p);
static inline
void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
{
+ struct sched_cache_group *grp = rcu_dereference_all(p->sched_cache_grp);
struct sched_cache_time *pcpu_sched;
- struct mm_struct *mm = p->mm;
int mm_sched_llc = -1;
unsigned long epoch;
@@ -1763,16 +1763,12 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
return;
/*
* init_task, kthreads and user thread created
- * by user_mode_thread() don't have mm.
- *
- * A kthread can temporarily adopt an mm via kthread_use_mm(),
- * so p->mm alone does not imply a user task.
+ * by user_mode_thread() don't have a cache group.
*/
- if (!mm || p->flags & PF_KTHREAD || !mm->sched_cache_grp ||
- !mm->sched_cache_grp->pcpu_sched)
+ if (!grp || p->flags & PF_KTHREAD || !grp->pcpu_sched)
return;
- pcpu_sched = per_cpu_ptr(mm->sched_cache_grp->pcpu_sched, cpu_of(rq));
+ pcpu_sched = per_cpu_ptr(grp->pcpu_sched, cpu_of(rq));
scoped_guard (raw_spinlock, &rq->cpu_epoch_lock) {
__update_mm_sched(rq, pcpu_sched);
@@ -1785,14 +1781,14 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
* If this process hasn't hit task_cache_work() for a while invalidate
* its preferred state.
*/
- if ((long)(epoch - READ_ONCE(mm->sched_cache_grp->epoch)) > llc_epoch_affinity_timeout ||
- invalid_llc_nr(mm, p, cpu_of(rq)) ||
- exceed_llc_capacity(mm, cpu_of(rq))) {
- if (READ_ONCE(mm->sched_cache_grp->cpu) != -1)
- WRITE_ONCE(mm->sched_cache_grp->cpu, -1);
+ if ((long)(epoch - READ_ONCE(grp->epoch)) > llc_epoch_affinity_timeout ||
+ invalid_llc_nr(grp, p, cpu_of(rq)) ||
+ exceed_llc_capacity(grp, cpu_of(rq))) {
+ if (READ_ONCE(grp->cpu) != -1)
+ WRITE_ONCE(grp->cpu, -1);
}
- mm_sched_llc = get_pref_llc(p, mm);
+ mm_sched_llc = get_pref_llc(p, grp);
/* task not on rq accounted later in account_entity_enqueue() */
if (task_running_on_cpu(rq->cpu, p) &&
@@ -1805,31 +1801,32 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
static void task_tick_cache(struct rq *rq, struct task_struct *p)
{
+ struct sched_cache_group *grp = rcu_dereference_all(p->sched_cache_grp);
struct callback_head *work = &p->cache_work;
- struct mm_struct *mm = p->mm;
unsigned long epoch;
if (!sched_cache_enabled())
return;
- if (!mm || p->flags & PF_KTHREAD ||
- !mm->sched_cache_grp->pcpu_sched)
+ if (!grp || p->flags & PF_KTHREAD ||
+ !grp->pcpu_sched)
return;
epoch = rq->cpu_epoch;
/* avoid moving backwards */
- if (time_after_eq(mm->sched_cache_grp->epoch, epoch))
+ if (time_after_eq(grp->epoch, epoch))
return;
- guard(raw_spinlock)(&mm->sched_cache_grp->lock);
+ guard(raw_spinlock)(&grp->lock);
if (work->next == work) {
task_work_add(p, work, TWA_RESUME);
- WRITE_ONCE(mm->sched_cache_grp->epoch, epoch);
+ WRITE_ONCE(grp->epoch, epoch);
}
}
-static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p)
+static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p,
+ struct sched_cache_group *grp)
{
#ifdef CONFIG_NUMA_BALANCING
int cpu, curr_cpu, nid, pref_nid;
@@ -1837,7 +1834,7 @@ static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p)
if (!static_branch_likely(&sched_numa_balancing))
goto out;
- cpu = READ_ONCE(p->mm->sched_cache_grp->cpu);
+ cpu = READ_ONCE(grp->cpu);
if (cpu != -1)
nid = cpu_to_node(cpu);
curr_cpu = task_cpu(p);
@@ -1898,9 +1895,7 @@ static void task_cache_work(struct callback_head *work)
unsigned long next_scan, now = jiffies;
struct task_struct *p = current, *cur;
unsigned long curr_m_a_occ = 0;
- struct mm_struct *mm = p->mm;
unsigned long m_a_occ = 0;
- cpumask_var_t cpus;
WARN_ON_ONCE(work != &p->cache_work);
@@ -1909,32 +1904,44 @@ static void task_cache_work(struct callback_head *work)
if (p->flags & PF_EXITING)
return;
- next_scan = READ_ONCE(mm->sched_cache_grp->next_scan);
+ /*
+ * A reference makes sure grp is not released by others. The rcu
+ * lock can not be held till after zalloc_cpumask_var() below,
+ * because the latter might sleep.
+ */
+ struct sched_cache_group *grp __free(sched_cache_group_put) =
+ task_cache_group_get(p);
+ if (!grp)
+ return;
+
+ next_scan = READ_ONCE(grp->next_scan);
if (time_before(now, next_scan))
return;
/* only 1 thread is allowed to scan */
- if (!try_cmpxchg(&mm->sched_cache_grp->next_scan, &next_scan,
+ if (!try_cmpxchg(&grp->next_scan, &next_scan,
now + max_t(unsigned long,
READ_ONCE(llc_epoch_period), 1)))
return;
curr_cpu = task_cpu(p);
- if (invalid_llc_nr(mm, p, curr_cpu) ||
- exceed_llc_capacity(mm, curr_cpu)) {
- if (READ_ONCE(mm->sched_cache_grp->cpu) != -1)
- WRITE_ONCE(mm->sched_cache_grp->cpu, -1);
+ if (invalid_llc_nr(grp, p, curr_cpu) ||
+ exceed_llc_capacity(grp, curr_cpu)) {
+ if (READ_ONCE(grp->cpu) != -1)
+ WRITE_ONCE(grp->cpu, -1);
return;
}
+ cpumask_var_t cpus __free(free_cpumask_var) = CPUMASK_VAR_NULL;
+
if (!zalloc_cpumask_var(&cpus, GFP_KERNEL))
return;
scoped_guard (cpus_read_lock) {
guard(rcu)();
- get_scan_cpumasks(cpus, p);
+ get_scan_cpumasks(cpus, p, grp);
for_each_cpu(cpu, cpus) {
/* XXX sched_cluster_active */
@@ -1946,8 +1953,6 @@ static void task_cache_work(struct callback_head *work)
continue;
for_each_cpu(i, sched_domain_span(sd)) {
- struct sched_cache_group *grp = mm->sched_cache_grp;
-
occ = fraction_mm_sched(cpu_rq(i),
per_cpu_ptr(grp->pcpu_sched, i));
a_occ += occ;
@@ -1956,9 +1961,13 @@ static void task_cache_work(struct callback_head *work)
m_cpu = i;
}
+ /*
+ * rcu_access_pointer() is used because the
+ * pointer is only compared, never dereferenced.
+ */
cur = rcu_dereference_all(cpu_rq(i)->curr);
if (cur && !(cur->flags & (PF_EXITING | PF_KTHREAD)) &&
- cur->mm == mm)
+ rcu_access_pointer(cur->sched_cache_grp) == grp)
nr_running++;
}
@@ -1982,7 +1991,7 @@ static void task_cache_work(struct callback_head *work)
m_a_cpu = m_cpu;
}
- if (llc_id(cpu) == llc_id(READ_ONCE(mm->sched_cache_grp->cpu)))
+ if (llc_id(cpu) == llc_id(READ_ONCE(grp->cpu)))
curr_m_a_occ = a_occ;
cpumask_andnot(cpus, cpus, sched_domain_span(sd));
@@ -2000,11 +2009,10 @@ static void task_cache_work(struct callback_head *work)
* 3. 2X is chosen based on test results, as it delivers
* the optimal performance gain so far.
*/
- WRITE_ONCE(mm->sched_cache_grp->cpu, m_a_cpu);
+ WRITE_ONCE(grp->cpu, m_a_cpu);
}
- update_avg_scale(&mm->sched_cache_grp->nr_running_avg, nr_running);
- free_cpumask_var(cpus);
+ update_avg_scale(&grp->nr_running_avg, nr_running);
}
void init_sched_mm(struct task_struct *p)
@@ -2013,6 +2021,13 @@ void init_sched_mm(struct task_struct *p)
init_task_work(work, task_cache_work);
work->next = work;
+ /*
+ * dup_task_struct() copies the parent's task_struct, including its
+ * sched_cache_grp, for which the child holds no reference. Clear it
+ * here - before copy_mm() runs - so the child never carries a
+ * borrowed pointer that the fork error path would put.
+ */
+ RCU_INIT_POINTER(p->sched_cache_grp, NULL);
/*
* Reset new task's preference to avoid
* polluting account_llc_enqueue().
@@ -3807,10 +3822,9 @@ static void task_numa_placement(struct task_struct *p)
* heuristic and occasional lost updates are tolerable.
*
* If a task exits, its corresponding footprint must
- * be subtracted from the mm->sched_cache_grp->footprint,
- * otherwise the mm->sched_cache_grp->footprint will not
- * converge: the exiting thread's footprint remains
- * unchanged/undecayed in mm->sched_cache_grp->footprint.
+ * be subtracted from p->sched_cache_grp->footprint,
+ * otherwise the footprint will not converge: the
+ * exiting thread's footprint remains unchanged/undecayed.
* See exit_mm().
*
* Lost updates and unsynchronized subtraction
@@ -3818,9 +3832,17 @@ static void task_numa_placement(struct task_struct *p)
* go negative. Clamp to zero to prevent the
* unsigned footprint from wrapping.
*/
- new_fp = (long)READ_ONCE(p->mm->sched_cache_grp->footprint) + diff;
- WRITE_ONCE(p->mm->sched_cache_grp->footprint,
- max(new_fp, 0L));
+ {
+ struct sched_cache_group *grp;
+
+ guard(rcu)();
+ grp = rcu_dereference(p->sched_cache_grp);
+
+ if (grp) {
+ new_fp = (long)READ_ONCE(grp->footprint) + diff;
+ WRITE_ONCE(grp->footprint, max(new_fp, 0L));
+ }
+ }
#endif
}
@@ -10730,23 +10752,23 @@ static enum llc_mig can_migrate_llc(int src_cpu, int dst_cpu,
static enum llc_mig can_migrate_llc_task(int src_cpu, int dst_cpu,
struct task_struct *p)
{
- struct mm_struct *mm;
+ struct sched_cache_group *grp;
bool to_pref;
int cpu;
- mm = p->mm;
- if (!mm || !mm->sched_cache_grp)
+ grp = rcu_dereference_all(p->sched_cache_grp);
+ if (!grp)
return mig_unrestricted;
- cpu = READ_ONCE(mm->sched_cache_grp->cpu);
+ cpu = READ_ONCE(grp->cpu);
if (cpu < 0 || cpus_share_cache(src_cpu, dst_cpu))
return mig_unrestricted;
/* skip cache aware load balance for too many threads */
- if (invalid_llc_nr(mm, p, dst_cpu) ||
- exceed_llc_capacity(mm, dst_cpu)) {
- if (READ_ONCE(mm->sched_cache_grp->cpu) != -1)
- WRITE_ONCE(mm->sched_cache_grp->cpu, -1);
+ if (invalid_llc_nr(grp, p, dst_cpu) ||
+ exceed_llc_capacity(grp, dst_cpu)) {
+ if (READ_ONCE(grp->cpu) != -1)
+ WRITE_ONCE(grp->cpu, -1);
return mig_unrestricted;
}
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index e656c7059bf8..8b67af28a471 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -4145,6 +4145,9 @@ static inline bool sched_cache_enabled(void)
return static_branch_unlikely(&sched_cache_active);
}
+DEFINE_FREE(sched_cache_group_put, struct sched_cache_group *,
+ sched_cache_group_put(_T));
+
extern void sched_cache_active_set(void);
#endif
--
2.32.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-02 0:04 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-02 0:08 [PATCH 0/2] sched/cache: Fix use after free mm access in account_mm_sched() Tim Chen
2026-09-02 0:08 ` [PATCH 1/2] sched/cache: Decouple sched_cache_group from mm Tim Chen
2026-09-02 0:08 ` [PATCH 2/2] sched/cache: Introduce task_struct->sched_cache_grp Tim Chen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox