mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
arm64 fixes:
- 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
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEE5RElWfyWxS+3PLO2a9axLQDIXvEFAmrk3ssACgkQa9axLQDI
XvFwiQ/+LSIVXGHJIWom8Wu6e3P+Ho0FxbDbxgT3VHXR6uHjdyNa/nN60IXVQES8
KJ7Kydrh8X+90eDYQY7iKwuXwNQsxwdYMFGLa26aFDaOv3bK0bI2xM3zpRAg98qy
UwXApfatt4iCm1pB8E8n3XJ64eVccapfrf6Mv5wnn1cg8vSsrlR6S3HotATGc8cG
+1cuoI3fgByDqiVkssJka2ZhM5tWBxEg4mQ3Un6O6vTQTAZCp/vbsRgOjupKsgRU
zLWh8KNEoV1ehYtPuwXQ+sRiOhfBHOuNK3i+FlmTEuRjcLJ2xbkpIzCEJvy2+n9+
n+0ewG0EkRrf4NodgeDmBB6zSyLhfKjorp634NqWj8UKF2z4uPLJzhb5xJXeeByZ
uOynRR19fj2YOWIc/vF1p+4yQCSi6eOEASU/5oYSqsnKU/As6Xoum22dMtRERB6e
YH8cQT0RXw0qWJ46+/6rRqIpugNa+kkVcQloP2YLKl4QpPYGhtK8Kw4dymx6OQcl
zDoiP8R2hQzylMjzvNRA+KugCOKsCPy4j7A3w3aiBVOkhOsfoHK4zkx3LJp/su/d
gRNiDJgN4S57yrmvEUlXmBXUI1QzeXnRNHm/xYb7SGe/DeDaJElS+EmcPFi/i6Up
VaKyApaS017oBq/pS5Ku1DL4jr8EjPM9xW7//Tczq4MzPyl4ogY=
=ZNuk
-----END PGP SIGNATURE-----
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
This commit is contained in:
commit
6e07889d8f
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
1
tools/testing/selftests/arm64/gcs/.gitignore
vendored
1
tools/testing/selftests/arm64/gcs/.gitignore
vendored
|
|
@ -5,3 +5,4 @@ gcs-stress
|
|||
gcs-stress-thread
|
||||
gcspushm
|
||||
gcsstr
|
||||
gcs-clone-pidfd
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
168
tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c
Normal file
168
tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c
Normal file
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user