summaryrefslogtreecommitdiffstats
path: root/mm/mseal.c
diff options
context:
space:
mode:
authorTim Chen <tim.c.chen@linux.intel.com>2026-09-21 17:37:22 -0700
committerIngo Molnar <mingo@kernel.org>2026-09-22 10:49:53 +0200
commit0d6526f82c3cdefcca47f73f5fc08dc6f335eac6 (patch)
tree176dc2d685debcfb082c151ee94f2425ac96b364 /mm/mseal.c
downloadlinux-stable-0d6526f82c3cdefcca47f73f5fc08dc6f335eac6.tar.gz
linux-stable-0d6526f82c3cdefcca47f73f5fc08dc6f335eac6.zip
sched/cache: Keep nr_pref_llc_running in the runnable domain, to fix LLC mis-scheduling buggrafted
alb_break_llc() decides whether to break LLC preference during active load balance. It does so by testing that every runnable fair task on the source rq prefers its LLC: env->src_rq->nr_pref_llc_running == env->src_rq->cfs.h_nr_runnable But the two counters cover different sets. nr_pref_llc_running is updated in account_llc_enqueue()/account_llc_dequeue(), next to cfs_rq->nr_queued, so it follows queued tasks. h_nr_runnable is updated in set_delayed()/ clear_delayed() and drops delay-dequeued tasks. So under DELAY_DEQUEUE, a preferring task that goes to sleep stays counted in nr_pref_llc_running while h_nr_runnable falls. The equality then breaks, alb_break_llc() returns false, and active balance is free to pull a task off its preferred LLC. Active balance only moves runnable tasks, and this is the only LLC check it consults: once the stopper runs, LBF_ACTIVE_LB skips the per-task test in can_migrate_task(). The runnable set is the one we want. Fix it on the counter side. A task should be counted in nr_pref_llc_running exactly while it is both queued on its preferred LLC (pref_llc_queued) and runnable (!sched_delayed). Define that membership once in task_pref_llc_runnable(), and adjust the counter only through pref_llc_running_inc()/pref_llc_running_dec() from the four sites that change either input: account_llc_enqueue(), account_llc_dequeue(), set_delayed() and clear_delayed(). Gating every update on the same predicate keeps the delay, wake and dequeue paths from double-counting or underflowing; see the comments at those sites for the ordering. nr_llc_running and sd->llc_counts are not touched and stay on queued semantics. Fixes: 714059f79ff0 ("sched/cache: Handle moving single tasks to/from their preferred LLC") Closes: https://lore.kernel.org/lkml/20260827135000.735138-1-zhanxusheng@xiaomi.com/ Reported-by: Zhan Xusheng <zhanxusheng@xiaomi.com> Suggested-by: Chen Yu <yu.c.chen@intel.com> Signed-off-by: Tim Chen <tim.c.chen@linux.intel.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Kayra Cizmeci <kayracizmeci@gmail.com> Cc: <stable@kernel.org> # v7.2.x Link: https://patch.msgid.link/06af61afedac32e6477f57feb4d658f6c411c3af.1790035273.git.tim.c.chen@linux.intel.com
Diffstat (limited to 'mm/mseal.c')
-rw-r--r--mm/mseal.c143
1 files changed, 143 insertions, 0 deletions
diff --git a/mm/mseal.c b/mm/mseal.c
new file mode 100644
index 000000000..7a8ac66dc
--- /dev/null
+++ b/mm/mseal.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Implement mseal() syscall.
+ *
+ * Copyright (c) 2023,2024 Google, Inc.
+ *
+ * Author: Jeff Xu <jeffxu@chromium.org>
+ */
+
+#include <linux/mempolicy.h>
+#include <linux/minmax.h>
+#include <linux/mman.h>
+#include <linux/mm.h>
+#include <linux/mm_inline.h>
+#include <linux/syscalls.h>
+#include <linux/sched.h>
+#include "internal.h"
+
+static bool range_contains_unmapped(unsigned long start, unsigned long end)
+{
+ VMA_ITERATOR(vmi, current->mm, start);
+ unsigned long prev_end = start;
+ struct vm_area_struct *vma;
+
+ for_each_vma_range(vmi, vma, end) {
+ if (vma->vm_start > prev_end)
+ return true;
+
+ prev_end = vma->vm_end;
+ }
+
+ return prev_end < end;
+}
+
+static int __mseal_range(unsigned long start, unsigned long end)
+{
+ VMA_ITERATOR(vmi, current->mm, start);
+ struct vm_area_struct *vma, *prev;
+
+ /* We know there are no gaps so this will be non-NULL. */
+ vma = vma_iter_load(&vmi);
+ prev = vma_prev(&vmi);
+ if (start > vma->vm_start)
+ prev = vma;
+
+ for_each_vma_range(vmi, vma, end) {
+ const unsigned long curr_start = max(vma->vm_start, start);
+ const unsigned long curr_end = min(vma->vm_end, end);
+
+ if (!vma_test(vma, VMA_SEALED_BIT)) {
+ vma_flags_t vma_flags = vma->flags;
+
+ vma_flags_set(&vma_flags, VMA_SEALED_BIT);
+
+ vma = vma_modify_flags(&vmi, prev, vma, curr_start,
+ curr_end, &vma_flags);
+ if (IS_ERR(vma))
+ return PTR_ERR(vma);
+ vma_start_write(vma);
+ vma_set_flags(vma, VMA_SEALED_BIT);
+ }
+
+ prev = vma;
+ }
+
+ return 0;
+}
+
+static int mseal_range(unsigned long start, unsigned long end)
+{
+ int err;
+
+ err = mmap_write_lock_killable(current->mm);
+ if (err)
+ return err;
+ if (range_contains_unmapped(start, end))
+ err = -ENOMEM;
+ else
+ err = __mseal_range(start, end);
+ mmap_write_unlock(current->mm);
+ return err;
+}
+
+/**
+ * mseal_mmap_page_zero() - If the MMAP_PAGE_ZERO personality is set, mseal()
+ * the page mapped at address zero.
+ */
+void mseal_mmap_page_zero(void)
+{
+ int err;
+
+ if (WARN_ON_ONCE(!(current->personality & MMAP_PAGE_ZERO)))
+ return;
+
+ err = mseal_range(0, PAGE_SIZE);
+ if (err)
+ pr_warn_ratelimited("pid=%d, couldn't seal address 0, ret=%d.\n",
+ task_pid_nr(current), err);
+}
+
+/*
+ * Seal VMAs in the specified input range to prevent an attacker replacing what
+ * is mapped in the range with something else.
+ *
+ * Disallows:
+ * - VMA unmapping, remapping or shrinking.
+ * - Overwriting the VMA with another one via mmap(), mremap() or similar.
+ * - Alteration of properties via mprotect()/pkey_mprotect().
+ * - Destructive madvise() behaviours (like MADV_DONTNEED) on anonymous read-only
+ * ranges.
+ *
+ * Since unmapped ranges can be mapped at any time, the input range must span
+ * mapped ranges only.
+ *
+ * The flags parameter is currently reserved.
+ */
+SYSCALL_DEFINE3(mseal, unsigned long, start, size_t, len, unsigned long, flags)
+{
+ size_t len_aligned;
+ unsigned long end;
+
+ /* Verify flags not set. */
+ if (flags)
+ return -EINVAL;
+
+ start = untagged_addr(start);
+ if (!PAGE_ALIGNED(start))
+ return -EINVAL;
+
+ len_aligned = PAGE_ALIGN(len);
+ /* Check to see whether len was rounded up from small -ve to zero. */
+ if (len && !len_aligned)
+ return -EINVAL;
+
+ end = start + len_aligned;
+ if (end < start)
+ return -EINVAL;
+
+ if (end == start)
+ return 0;
+
+ return mseal_range(start, end);
+}