diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-30 09:46:29 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-30 09:46:29 -1000 |
| commit | 6e07889d8fedf729282ed14ff202e06dbb67444b (patch) | |
| tree | 24fe8a9ae9157959829d30592dcdda7445c9a3a9 | |
| parent | 780cb2a5fd64901444878cf5a40c0f2ec54837ce (diff) | |
| parent | f46be4da2cbb17574b5369517a9a1e1b414063b4 (diff) | |
Merge tag 'arm64-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux
Pull arm64 fixes from Catalin Marinas:
"Three fixes that turned up during the merging window: shadow stack not
freed during a failed fork or it is unmapped from parent when a worker
thread exits (same commit covers both); PTE_CONT set on PROT_NONE
entries but subsequently ignored by the unfolding code since
pte_valid() is false (pte_present() is true); an MPAM lock not
released on the error path.
Summary:
- Release the GCS on a failed fork.
copy_thread() can allocate a GCS for CLONE_VM threads but a later
copy_process() failure left the VMA in the parent's mm. Implement
exit_thread() to free it and stop kernel threads and user workers
inheriting the parent's GCS base/size. Add a kselftest for the
clone3() failure case
- Do not set PTE_CONT on invalid PTEs in contpte_set_ptes().
Batched set_ptes() of present-invalid entries (e.g. PROT_NONE)
created contpte groups that the single-PTE helpers did not unfold,
corrupting a neighbouring guard marker via
test_and_clear_young_ptes()
- Release the MPAM mon_sel lock on the mpam_save_mbwu_state() error
paths"
* tag 'arm64-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux:
selftests/arm64: GCS must not leak on a failed clone3
arm64/gcs: release GCS on failed fork
arm64/mm: restrict transparent contpte mappings to valid PTEs
arm_mpam: Release the mon_sel lock on mpam_save_mbwu_state() errors
| -rw-r--r-- | arch/arm64/Kconfig | 1 | ||||
| -rw-r--r-- | arch/arm64/include/asm/pgtable.h | 3 | ||||
| -rw-r--r-- | arch/arm64/kernel/process.c | 17 | ||||
| -rw-r--r-- | arch/arm64/mm/contpte.c | 4 | ||||
| -rw-r--r-- | drivers/resctrl/mpam_devices.c | 12 | ||||
| -rw-r--r-- | tools/testing/selftests/arm64/gcs/.gitignore | 1 | ||||
| -rw-r--r-- | tools/testing/selftests/arm64/gcs/Makefile | 3 | ||||
| -rw-r--r-- | tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c | 168 | ||||
| -rw-r--r-- | tools/testing/selftests/arm64/gcs/gcs-locking.c | 20 | ||||
| -rw-r--r-- | tools/testing/selftests/arm64/gcs/gcs-util.h | 24 | ||||
| -rw-r--r-- | tools/testing/selftests/arm64/gcs/libc-gcs.c | 20 |
11 files changed, 224 insertions, 49 deletions
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index cfef22aee709..1075bc661833 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -199,6 +199,7 @@ config ARM64 select HAVE_SHARED_GPIOS select HAVE_BUILDTIME_MCOUNT_SORT select HAVE_EFFICIENT_UNALIGNED_ACCESS + select HAVE_EXIT_THREAD select HAVE_GUP_FAST select HAVE_FTRACE_GRAPH_FUNC select HAVE_FUNCTION_TRACER diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h index 763c5a411d64..a474c9fb485a 100644 --- a/arch/arm64/include/asm/pgtable.h +++ b/arch/arm64/include/asm/pgtable.h @@ -1597,6 +1597,9 @@ extern void modify_prot_commit_ptes(struct vm_area_struct *vma, unsigned long ad * The contpte APIs are used to transparently manage the contiguous bit in ptes * where it is possible and makes sense to do so. The PTE_CONT bit is considered * a private implementation detail of the public ptep API (see below). + * Single-PTE transparent contpte helpers use pte_valid_cont() to identify + * grouped PTEs; explicit contiguous-encoding users such as hugetlb manage + * their entries separately. */ extern void __contpte_try_fold(struct mm_struct *mm, unsigned long addr, pte_t *ptep, pte_t pte); diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c index 581f80e9b9b7..42b15b0c069f 100644 --- a/arch/arm64/kernel/process.c +++ b/arch/arm64/kernel/process.c @@ -320,9 +320,18 @@ static int copy_thread_gcs(struct task_struct *p, return 0; } +static void gcs_reset_thread(struct task_struct *task) +{ + task->thread.gcs_base = 0; + task->thread.gcs_size = 0; + task->thread.gcspr_el0 = 0; +} + #else static void flush_gcs(void) { } +static void gcs_reset_thread(struct task_struct *task) { } + static int copy_thread_gcs(struct task_struct *p, const struct kernel_clone_args *args) { @@ -341,6 +350,12 @@ void flush_thread(void) flush_gcs(); } +void exit_thread(struct task_struct *tsk) +{ + /* Failed fork after copy_thread(); deactivate_mm() does not run. */ + gcs_free(tsk); +} + void arch_release_task_struct(struct task_struct *tsk) { fpsimd_release_task(tsk); @@ -503,6 +518,8 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) p->thread.cpu_context.x19 = (unsigned long)args->fn; p->thread.cpu_context.x20 = (unsigned long)args->fn_arg; + /* Kernel threads sharing the parent's mm must not free its GCS. */ + gcs_reset_thread(p); if (system_supports_poe()) p->thread.por_el0 = POR_EL0_INIT; diff --git a/arch/arm64/mm/contpte.c b/arch/arm64/mm/contpte.c index 0acab179fc1a..be78d8602e51 100644 --- a/arch/arm64/mm/contpte.c +++ b/arch/arm64/mm/contpte.c @@ -460,8 +460,10 @@ void contpte_set_ptes(struct mm_struct *mm, unsigned long addr, */ VM_WARN_ON(nr == 1); - if (!mm_is_user(mm)) + if (!mm_is_user(mm) || !pte_valid(pte)) { + VM_WARN_ON_ONCE(!pte_valid(pte) && pte_cont(pte)); return __set_ptes(mm, addr, ptep, pte, nr); + } end = addr + (nr << PAGE_SHIFT); pfn = pte_pfn(pte); diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 48adc49b833c..c067b6d57d71 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -2037,8 +2037,10 @@ static int mpam_save_mbwu_state(void *arg) struct mpam_class *class = ris->vmsc->comp->class; for (i = 0; i < ris->props.num_mbwu_mon; i++) { - if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc))) - return -EIO; + ACQUIRE(mon_sel_lock, guard)(msc); + ret = ACQUIRE_ERR(mon_sel_lock, &guard); + if (ret) + return ret; mbwu_state = &ris->mbwu_state[i]; cfg = &mbwu_state->cfg; @@ -2061,10 +2063,8 @@ static int mpam_save_mbwu_state(void *arg) cfg->partid = FIELD_GET(MSMON_CFG_x_FLT_PARTID, cur_flt); mbwu_state->enabled = FIELD_GET(MSMON_CFG_x_CTL_EN, cur_ctl); - if (!mbwu_state->enabled) { - mpam_mon_sel_unlock(msc); + if (!mbwu_state->enabled) continue; - } val = 0; mbwu_arg = (struct mon_read) { @@ -2088,8 +2088,6 @@ static int mpam_save_mbwu_state(void *arg) ret = mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0); if (ret) return ret; - - mpam_mon_sel_unlock(msc); } return 0; diff --git a/tools/testing/selftests/arm64/gcs/.gitignore b/tools/testing/selftests/arm64/gcs/.gitignore index bbb8e40a7e52..063780ab5779 100644 --- a/tools/testing/selftests/arm64/gcs/.gitignore +++ b/tools/testing/selftests/arm64/gcs/.gitignore @@ -5,3 +5,4 @@ gcs-stress gcs-stress-thread gcspushm gcsstr +gcs-clone-pidfd diff --git a/tools/testing/selftests/arm64/gcs/Makefile b/tools/testing/selftests/arm64/gcs/Makefile index 1fbbf0ca1f02..f1365eea31c9 100644 --- a/tools/testing/selftests/arm64/gcs/Makefile +++ b/tools/testing/selftests/arm64/gcs/Makefile @@ -6,7 +6,8 @@ # nolibc. # -TEST_GEN_PROGS := basic-gcs libc-gcs gcs-locking gcs-stress gcspushm gcsstr +TEST_GEN_PROGS := basic-gcs libc-gcs gcs-locking gcs-stress gcspushm gcsstr \ + gcs-clone-pidfd TEST_GEN_PROGS_EXTENDED := gcs-stress-thread LDLIBS+=-lpthread diff --git a/tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c b/tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c new file mode 100644 index 000000000000..96400ea376c0 --- /dev/null +++ b/tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * clone3(CLONE_VM|CLONE_PIDFD) with an unwritable pidfd pointer must + * not leak a GCS VMA. copy_thread() maps one, then put_user() fails + * and copy_process() calls exit_thread(). Without HAVE_EXIT_THREAD + * that free is a no-op and the VMA stays in the parent's mm. + * + * After enabling GCS, do not return from main(): libc frames above + * that point have no GCS entries. + */ +#define _GNU_SOURCE + +#include <errno.h> +#include <linux/sched.h> +#include <sched.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/auxv.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <asm/hwcap.h> + +#include "kselftest.h" +#include "gcs-util.h" + +#ifndef HWCAP_GCS +#define HWCAP_GCS (1UL << 32) +#endif + +#ifndef __NR_clone3 +#define __NR_clone3 435 +#endif + +#define NCLONE 16 +#define STACK_SIZE (64 * 1024) + +static int count_ss_vmas(void) +{ + FILE *f; + char line[256]; + int vmas = 0; + + f = fopen("/proc/self/smaps", "r"); + if (!f) + ksft_exit_fail_msg("fopen /proc/self/smaps: %s\n", + strerror(errno)); + + while (fgets(line, sizeof(line), f)) { + if (!strncmp(line, "VmFlags:", 8) && strstr(line, " ss")) + vmas++; + } + fclose(f); + return vmas; +} + +/* + * CLONE_VM maps a new GCS. CLONE_PIDFD lets put_user() fail after that. + * Use a raw svc: the child resumes on an empty GCS, so a libc return + * after clone3 would fault; exit the same way. + */ +static long clone_pidfd(void *stack, size_t stack_size, int *pidfd_ptr) +{ + struct clone_args args = { + .flags = CLONE_VM | CLONE_PIDFD, + .pidfd = (uint64_t)(uintptr_t)pidfd_ptr, + .exit_signal = SIGCHLD, + .stack = (uint64_t)(uintptr_t)stack, + .stack_size = stack_size, + }; + register long num __asm__("x8") = __NR_clone3; + register long ret __asm__("x0") = (long)&args; + register long sz __asm__("x1") = sizeof(args); + int st; + + asm volatile("svc #0" + : "+r"(ret) + : "r"(num), "r"(sz) + : "memory", "cc"); + if (ret == 0) { + register long n __asm__("x8") = __NR_exit; + register long e __asm__("x0") = 0; + + asm volatile("svc #0" : : "r"(n), "r"(e) : "memory"); + __builtin_unreachable(); + } + if (ret < 0) + return ret; + if (waitpid((pid_t)ret, &st, 0) < 0) + return -errno; + if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) + return 1; + return 0; +} + +int main(void) +{ + void *stack; + void *bad_pidfd; + unsigned long gcs_mode; + long rc; + int pidfd = -1; + int vmas0, vmas1; + int i, n_efault = 0; + + ksft_print_header(); + + if (!(getauxval(AT_HWCAP) & HWCAP_GCS)) + ksft_exit_skip("GCS not supported\n"); + + stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (stack == MAP_FAILED) + ksft_exit_fail_msg("mmap stack: %s\n", strerror(errno)); + + bad_pidfd = mmap(NULL, 4096, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (bad_pidfd == MAP_FAILED) + ksft_exit_fail_msg("mmap pidfd page: %s\n", strerror(errno)); + + rc = my_syscall2(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode); + if (rc) + ksft_exit_fail_msg("PR_GET_SHADOW_STACK_STATUS: %ld\n", rc); + + if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) { + rc = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, + PR_SHADOW_STACK_ENABLE); + if (rc == -EOPNOTSUPP || rc == -EINVAL || rc == -EBUSY) + ksft_exit_skip("cannot enable GCS: %ld\n", rc); + if (rc) + ksft_exit_fail_msg("PR_SET_SHADOW_STACK_STATUS: %ld\n", + rc); + } + + rc = clone_pidfd(stack, STACK_SIZE, &pidfd); + if (rc == -ENOSYS || rc == -EINVAL || rc == -EPERM) + ksft_exit_skip("clone3 valid pidfd unavailable: %s\n", + strerror(-rc)); + + ksft_set_plan(3); + + ksft_test_result(rc == 0 && pidfd >= 0, + "clone3 valid pidfd (rc=%ld pidfd=%d)\n", + rc, pidfd); + if (pidfd >= 0) + close(pidfd); + + vmas0 = count_ss_vmas(); + + for (i = 0; i < NCLONE; i++) { + rc = clone_pidfd(stack, STACK_SIZE, bad_pidfd); + if (rc == -EFAULT) + n_efault++; + } + ksft_test_result(n_efault == NCLONE, + "clone3 bad pidfd returns -EFAULT x%d\n", NCLONE); + + vmas1 = count_ss_vmas(); + ksft_test_result(vmas1 == vmas0, + "no GCS VMA leak (%d -> %d)\n", vmas0, vmas1); + + ksft_finished(); +} diff --git a/tools/testing/selftests/arm64/gcs/gcs-locking.c b/tools/testing/selftests/arm64/gcs/gcs-locking.c index 80a1317f6097..8877fd2d2eab 100644 --- a/tools/testing/selftests/arm64/gcs/gcs-locking.c +++ b/tools/testing/selftests/arm64/gcs/gcs-locking.c @@ -18,26 +18,6 @@ #include "gcs-util.h" -#define my_syscall2(num, arg1, arg2) \ -({ \ - register long _num __asm__ ("x8") = (num); \ - register long _arg1 __asm__ ("x0") = (long)(arg1); \ - register long _arg2 __asm__ ("x1") = (long)(arg2); \ - register long _arg3 __asm__ ("x2") = 0; \ - register long _arg4 __asm__ ("x3") = 0; \ - register long _arg5 __asm__ ("x4") = 0; \ - \ - __asm__ volatile ( \ - "svc #0\n" \ - : "=r"(_arg1) \ - : "r"(_arg1), "r"(_arg2), \ - "r"(_arg3), "r"(_arg4), \ - "r"(_arg5), "r"(_num) \ - : "memory", "cc" \ - ); \ - _arg1; \ -}) - /* No mode bits are rejected for locking */ TEST(lock_all_modes) { diff --git a/tools/testing/selftests/arm64/gcs/gcs-util.h b/tools/testing/selftests/arm64/gcs/gcs-util.h index 7a81bb07ed4b..46f6ed9cf54d 100644 --- a/tools/testing/selftests/arm64/gcs/gcs-util.h +++ b/tools/testing/selftests/arm64/gcs/gcs-util.h @@ -43,6 +43,30 @@ #define GCS_CAP(x) (((unsigned long)(x) & GCS_CAP_ADDR_MASK) | \ GCS_CAP_VALID_TOKEN) +/* + * prctl(..., arg1, arg2) with unused registers forced to zero. The + * kernel rejects non-zero unused args; libc may leave garbage there. + */ +#define my_syscall2(num, arg1, arg2) \ +({ \ + register long _num __asm__ ("x8") = (num); \ + register long _arg1 __asm__ ("x0") = (long)(arg1); \ + register long _arg2 __asm__ ("x1") = (long)(arg2); \ + register long _arg3 __asm__ ("x2") = 0; \ + register long _arg4 __asm__ ("x3") = 0; \ + register long _arg5 __asm__ ("x4") = 0; \ + \ + __asm__ volatile ( \ + "svc #0\n" \ + : "=r"(_arg1) \ + : "r"(_arg1), "r"(_arg2), \ + "r"(_arg3), "r"(_arg4), \ + "r"(_arg5), "r"(_num) \ + : "memory", "cc" \ + ); \ + _arg1; \ +}) + static inline unsigned long *get_gcspr(void) { unsigned long *gcspr; diff --git a/tools/testing/selftests/arm64/gcs/libc-gcs.c b/tools/testing/selftests/arm64/gcs/libc-gcs.c index ddb0b2b20155..4e9fa9b82f0f 100644 --- a/tools/testing/selftests/arm64/gcs/libc-gcs.c +++ b/tools/testing/selftests/arm64/gcs/libc-gcs.c @@ -24,26 +24,6 @@ #include "gcs-util.h" -#define my_syscall2(num, arg1, arg2) \ -({ \ - register long _num __asm__ ("x8") = (num); \ - register long _arg1 __asm__ ("x0") = (long)(arg1); \ - register long _arg2 __asm__ ("x1") = (long)(arg2); \ - register long _arg3 __asm__ ("x2") = 0; \ - register long _arg4 __asm__ ("x3") = 0; \ - register long _arg5 __asm__ ("x4") = 0; \ - \ - __asm__ volatile ( \ - "svc #0\n" \ - : "=r"(_arg1) \ - : "r"(_arg1), "r"(_arg2), \ - "r"(_arg3), "r"(_arg4), \ - "r"(_arg5), "r"(_num) \ - : "memory", "cc" \ - ); \ - _arg1; \ -}) - static noinline void gcs_recurse(int depth) { if (depth) |
