summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-30 09:46:29 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-30 09:46:29 -1000
commit6e07889d8fedf729282ed14ff202e06dbb67444b (patch)
tree24fe8a9ae9157959829d30592dcdda7445c9a3a9
parent780cb2a5fd64901444878cf5a40c0f2ec54837ce (diff)
parentf46be4da2cbb17574b5369517a9a1e1b414063b4 (diff)
downloadlinux-6e07889d8fedf729282ed14ff202e06dbb67444b.tar.gz
linux-6e07889d8fedf729282ed14ff202e06dbb67444b.zip
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/Kconfig1
-rw-r--r--arch/arm64/include/asm/pgtable.h3
-rw-r--r--arch/arm64/kernel/process.c17
-rw-r--r--arch/arm64/mm/contpte.c4
-rw-r--r--drivers/resctrl/mpam_devices.c12
-rw-r--r--tools/testing/selftests/arm64/gcs/.gitignore1
-rw-r--r--tools/testing/selftests/arm64/gcs/Makefile3
-rw-r--r--tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c168
-rw-r--r--tools/testing/selftests/arm64/gcs/gcs-locking.c20
-rw-r--r--tools/testing/selftests/arm64/gcs/gcs-util.h24
-rw-r--r--tools/testing/selftests/arm64/gcs/libc-gcs.c20
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)