mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 14:04:27 -04:00
Merge branch 'for-next/selftests' into for-next/core
* for-next/selftests: tools: Ensure tools copy of linux/filter.h exports the UAPI kselftest/arm64: Fix abi test compilation errors kselftest/arm64: Don't write to P0 in irritator on SME only systems kselftest/arm64: Add testcase for SECCOMP_RET_TRACE orig_x0 bypass kselftest/arm64: Add seccomp ptrace x0 bypass test kselftest/arm64: fp-ptrace: Fix checks for inactive SVE and SSVE regsets
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#define __TOOLS_LINUX_FILTER_H
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <uapi/linux/filter.h>
|
||||
|
||||
/* ArgX, context and stack frame pointer register positions. Note,
|
||||
* Arg1, Arg2, Arg3, etc are used as argument mappings of function
|
||||
|
||||
2
tools/testing/selftests/arm64/abi/.gitignore
vendored
2
tools/testing/selftests/arm64/abi/.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
hwcap
|
||||
ptrace
|
||||
seccomp_ptrace_x0_bypass
|
||||
seccomp_ret_trace_x0_bypass
|
||||
syscall-abi
|
||||
tpidr2
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2021 ARM Limited
|
||||
|
||||
TEST_GEN_PROGS := hwcap ptrace syscall-abi tpidr2
|
||||
CFLAGS += $(KHDR_INCLUDES) -I$(top_srcdir)/tools/include
|
||||
|
||||
TEST_GEN_PROGS := hwcap ptrace syscall-abi tpidr2 seccomp_ptrace_x0_bypass seccomp_ret_trace_x0_bypass
|
||||
|
||||
include ../../lib.mk
|
||||
|
||||
|
||||
192
tools/testing/selftests/arm64/abi/seccomp_ptrace_x0_bypass.c
Normal file
192
tools/testing/selftests/arm64/abi/seccomp_ptrace_x0_bypass.c
Normal file
@@ -0,0 +1,192 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Test that seccomp, tracepoints and audit observe the correct syscall
|
||||
* arguments after a ptracer has modified them at syscall-enter-stop.
|
||||
*
|
||||
* On arm64, both the first argument and the return value of a syscall
|
||||
* are passed in register x0. The original x0 is saved in
|
||||
* pt_regs::orig_x0 during syscall entry and returned as the first
|
||||
* argument by syscall_get_arguments(). Because ptrace modifications
|
||||
* to x0 are not automatically reflected in orig_x0, seccomp, tracepoints
|
||||
* and audit may see a stale value unless orig_x0 is explicitly
|
||||
* re-synchronised after a ptrace stop.
|
||||
*
|
||||
* This test sets up a seccomp filter that allows write(2, ...) but kills
|
||||
* the task for any other fd. A ptracer changes the fd argument from 2
|
||||
* to 1 at the syscall-enter stop. If the orig_x0 re-sync works, seccomp
|
||||
* sees the modified argument (fd=1) and kills the child with SIGSYS
|
||||
* (test passes). If orig_x0 is not re-synced, seccomp sees the original
|
||||
* fd=2, the write succeeds and the child exits normally (test fails,
|
||||
* vulnerability present).
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
#include <asm/ptrace.h>
|
||||
#include <linux/elf.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/seccomp.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
#include "kselftest.h"
|
||||
|
||||
#define EXPECTED_TESTS 1
|
||||
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
#define ARG0_OFFSET (offsetof(struct seccomp_data, args))
|
||||
#else
|
||||
#define ARG0_OFFSET (offsetof(struct seccomp_data, args) + 4)
|
||||
#endif
|
||||
|
||||
static int do_child(void)
|
||||
{
|
||||
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL))
|
||||
ksft_exit_fail_perror("PTRACE_TRACEME");
|
||||
|
||||
if (raise(SIGSTOP))
|
||||
ksft_exit_fail_perror("raise(SIGSTOP)");
|
||||
|
||||
/*
|
||||
* Seccomp filter:
|
||||
* If syscall is not write -> ALLOW
|
||||
* If syscall is write:
|
||||
* - If args[0] (fd) == 2 -> ALLOW
|
||||
* - Otherwise -> KILL
|
||||
*/
|
||||
struct sock_filter filter[] = {
|
||||
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), /* nr */
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 3),
|
||||
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, ARG0_OFFSET), /* args[0] */
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 2, 1, 0),
|
||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
|
||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
||||
};
|
||||
struct sock_fprog prog = {
|
||||
.len = ARRAY_SIZE(filter),
|
||||
.filter = filter,
|
||||
};
|
||||
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
|
||||
ksft_exit_fail_perror("prctl NO_NEW_PRIVS");
|
||||
|
||||
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog))
|
||||
ksft_exit_fail_perror("prctl SECCOMP");
|
||||
|
||||
/*
|
||||
* Invoke write(2, ...) while the tracer will change the first
|
||||
* argument (fd) from 2 to 1 at syscall entry.
|
||||
*/
|
||||
syscall(__NR_write, 2, NULL, 0);
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
static int do_parent(pid_t child)
|
||||
{
|
||||
bool bypass = false;
|
||||
int status;
|
||||
|
||||
/* Wait for the initial SIGSTOP */
|
||||
if (waitpid(child, &status, 0) != child)
|
||||
ksft_exit_fail_msg("waitpid failed");
|
||||
|
||||
if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP)
|
||||
ksft_exit_fail_msg("unexpected stop status");
|
||||
|
||||
if (ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_EXITKILL))
|
||||
ksft_exit_fail_perror("PTRACE_SETOPTIONS");
|
||||
|
||||
if (ptrace(PTRACE_SYSCALL, child, 0, 0))
|
||||
ksft_exit_fail_perror("PTRACE_SYSCALL");
|
||||
|
||||
while (1) {
|
||||
int sig;
|
||||
|
||||
if (waitpid(child, &status, 0) != child)
|
||||
ksft_exit_fail_msg("waitpid lost child");
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
/* Child exited normally – bypass succeeded */
|
||||
bypass = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (WIFSIGNALED(status)) {
|
||||
sig = WTERMSIG(status);
|
||||
if (sig == SIGSYS)
|
||||
break;
|
||||
ksft_exit_fail_msg("child died unexpectedly from signal %d (%s)",
|
||||
sig, strsignal(sig));
|
||||
}
|
||||
|
||||
if (!WIFSTOPPED(status))
|
||||
ksft_exit_fail_msg("unexpected wait status");
|
||||
|
||||
sig = WSTOPSIG(status);
|
||||
|
||||
if (sig == (SIGTRAP | 0x80)) {
|
||||
struct user_regs_struct regs;
|
||||
struct iovec iov = {
|
||||
.iov_base = ®s,
|
||||
.iov_len = sizeof(regs),
|
||||
};
|
||||
|
||||
if (ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov))
|
||||
ksft_exit_fail_perror("PTRACE_GETREGSET");
|
||||
|
||||
unsigned long syscall_nr = regs.regs[8];
|
||||
unsigned long x0 = regs.regs[0];
|
||||
|
||||
/* Modify fd from 2 to 1 at write entry */
|
||||
if (syscall_nr == __NR_write && x0 == 2) {
|
||||
regs.regs[0] = 1;
|
||||
if (ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &iov))
|
||||
ksft_exit_fail_perror("PTRACE_SETREGSET");
|
||||
}
|
||||
|
||||
if (ptrace(PTRACE_SYSCALL, child, 0, 0))
|
||||
ksft_exit_fail_perror("PTRACE_SYSCALL");
|
||||
} else {
|
||||
/* Forward other signals */
|
||||
if (ptrace(PTRACE_SYSCALL, child, 0, sig))
|
||||
ksft_exit_fail_perror("PTRACE_SYSCALL");
|
||||
}
|
||||
}
|
||||
|
||||
/* bypass == true means vulnerability exists -> test fails */
|
||||
return bypass ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
pid_t child;
|
||||
|
||||
ksft_print_header();
|
||||
ksft_set_plan(EXPECTED_TESTS);
|
||||
|
||||
child = fork();
|
||||
if (child < 0)
|
||||
ksft_exit_fail_msg("fork failed: %s", strerror(errno));
|
||||
|
||||
if (!child)
|
||||
return do_child();
|
||||
|
||||
/*
|
||||
* do_parent() returns EXIT_SUCCESS if the child was killed by
|
||||
* SIGSYS (i.e. seccomp correctly saw the modified argument),
|
||||
* and EXIT_FAILURE if the child exited normally (bypass).
|
||||
*/
|
||||
int result = do_parent(child);
|
||||
|
||||
ksft_test_result(result == EXIT_SUCCESS, "seccomp_ptrace_x0_bypass\n");
|
||||
|
||||
ksft_print_cnts();
|
||||
return result;
|
||||
}
|
||||
201
tools/testing/selftests/arm64/abi/seccomp_ret_trace_x0_bypass.c
Normal file
201
tools/testing/selftests/arm64/abi/seccomp_ret_trace_x0_bypass.c
Normal file
@@ -0,0 +1,201 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Test for SECCOMP_RET_TRACE argument modification bypass
|
||||
* via stale orig_x0 during filter re-evaluation.
|
||||
*
|
||||
* On arm64, syscall_get_arguments() reads the first argument from
|
||||
* regs->orig_x0. When a seccomp filter returns SECCOMP_RET_TRACE,
|
||||
* ptrace may modify regs->regs[0] while orig_x0 remains unchanged.
|
||||
* The kernel then re-evaluates the filter; if it sees the stale
|
||||
* orig_x0, it may incorrectly allow a syscall that the tracer intended
|
||||
* to block.
|
||||
*
|
||||
* This test installs a filter that:
|
||||
* - TRACEs write() when fd == 2
|
||||
* - returns ERRNO(EPERM) when fd == 1
|
||||
* - allows all other syscalls
|
||||
*
|
||||
* The child calls write(2, ...). The parent catches the SECCOMP stop,
|
||||
* changes x0 (fd) from 2 to 1, and resumes the child.
|
||||
*
|
||||
* If re-evaluation sees the old fd=2 (stale orig_x0), the filter
|
||||
* returns TRACE again; because recheck_after_trace is true, the kernel
|
||||
* allows the syscall to proceed. write(1, ...) succeeds, child exits 0.
|
||||
* -> test FAIL (bypass detected).
|
||||
*
|
||||
* If re-evaluation sees the new fd=1 (synced orig_x0), the filter
|
||||
* returns ERRNO(EPERM), write fails, child exits 1.
|
||||
* -> test PASS (no bypass).
|
||||
*
|
||||
* No special privileges required beyond CAP_SYS_PTRACE.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
#include <linux/elf.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/seccomp.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
#include "kselftest.h"
|
||||
|
||||
#define PTRACE_EVENT_MASK(status) ((status) >> 16)
|
||||
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
#define ARG0_OFFSET (offsetof(struct seccomp_data, args))
|
||||
#else
|
||||
#define ARG0_OFFSET (offsetof(struct seccomp_data, args) + 4)
|
||||
#endif
|
||||
|
||||
static int do_child(void)
|
||||
{
|
||||
long ret;
|
||||
|
||||
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL))
|
||||
_exit(2);
|
||||
|
||||
raise(SIGSTOP); /* synchronize with parent */
|
||||
|
||||
/*
|
||||
* Filter:
|
||||
* if syscall == write:
|
||||
* if fd == 2 -> TRACE
|
||||
* if fd == 1 -> ERRNO(EPERM)
|
||||
* else -> ALLOW
|
||||
* else -> ALLOW
|
||||
*/
|
||||
struct sock_filter filter[] = {
|
||||
/* Load syscall number */
|
||||
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
|
||||
/* If not write, allow */
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 5),
|
||||
/* Load first argument (fd) */
|
||||
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, ARG0_OFFSET),
|
||||
/* fd == 2 ? */
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 2, 0, 1),
|
||||
/* Yes: TRACE */
|
||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE),
|
||||
/* fd == 1 ? */
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, 1),
|
||||
/* Yes: ERRNO(EPERM) */
|
||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM & SECCOMP_RET_DATA)),
|
||||
/* Other fd: ALLOW */
|
||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
||||
};
|
||||
|
||||
struct sock_fprog prog = {
|
||||
.len = ARRAY_SIZE(filter),
|
||||
.filter = filter,
|
||||
};
|
||||
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
|
||||
_exit(3);
|
||||
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog))
|
||||
_exit(4);
|
||||
|
||||
/*
|
||||
* write(2, ...) triggers TRACE, parent changes fd to 1.
|
||||
* If re-eval sees fd=1 -> ERRNO -> write fails, ret = -EPERM.
|
||||
* If re-eval sees fd=2 -> TRACE again -> allowed -> write succeeds.
|
||||
*/
|
||||
ret = syscall(__NR_write, 2, "", 0);
|
||||
_exit(ret == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct user_pt_regs regs;
|
||||
struct iovec iov = { .iov_base = ®s, .iov_len = sizeof(regs) };
|
||||
pid_t child;
|
||||
int status;
|
||||
|
||||
ksft_print_header();
|
||||
ksft_set_plan(1);
|
||||
|
||||
child = fork();
|
||||
if (child < 0)
|
||||
ksft_exit_fail_msg("fork failed: %s", strerror(errno));
|
||||
|
||||
if (!child)
|
||||
return do_child();
|
||||
|
||||
/* 1. Wait for initial SIGSTOP */
|
||||
if (waitpid(child, &status, 0) != child)
|
||||
ksft_exit_fail_msg("waitpid SIGSTOP");
|
||||
if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP)
|
||||
ksft_exit_fail_msg("unexpected initial stop");
|
||||
|
||||
/* 2. Enable SECCOMP ptrace events */
|
||||
if (ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESECCOMP))
|
||||
ksft_exit_fail_msg("PTRACE_SETOPTIONS");
|
||||
|
||||
/* 3. Continue child to hit SECCOMP stop */
|
||||
if (ptrace(PTRACE_CONT, child, 0, 0))
|
||||
ksft_exit_fail_msg("PTRACE_CONT");
|
||||
|
||||
/* 4. Wait for SECCOMP stop */
|
||||
while (1) {
|
||||
if (waitpid(child, &status, 0) != child)
|
||||
ksft_exit_fail_msg("waitpid SECCOMP");
|
||||
if (WIFEXITED(status)) {
|
||||
ksft_test_result_fail("child exited before SECCOMP stop\n");
|
||||
goto out;
|
||||
}
|
||||
if (WIFSIGNALED(status)) {
|
||||
ksft_test_result_fail("child killed unexpectedly\n");
|
||||
goto out;
|
||||
}
|
||||
if (WIFSTOPPED(status) &&
|
||||
WSTOPSIG(status) == SIGTRAP &&
|
||||
PTRACE_EVENT_MASK(status) == PTRACE_EVENT_SECCOMP)
|
||||
break;
|
||||
ptrace(PTRACE_CONT, child, 0, WSTOPSIG(status));
|
||||
}
|
||||
|
||||
/* 5. Modify x0 (fd) from 2 to 1 */
|
||||
if (ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov))
|
||||
ksft_exit_fail_perror("GETREGSET");
|
||||
if (regs.regs[8] != __NR_write || regs.regs[0] != 2) {
|
||||
ksft_test_result_fail("unexpected regs: syscall=%llu, x0=%llu\n",
|
||||
regs.regs[8], regs.regs[0]);
|
||||
goto out;
|
||||
}
|
||||
regs.regs[0] = 1;
|
||||
if (ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &iov))
|
||||
ksft_exit_fail_perror("SETREGSET");
|
||||
|
||||
/* 6. Resume child */
|
||||
if (ptrace(PTRACE_CONT, child, 0, 0))
|
||||
ksft_exit_fail_perror("PTRACE_CONT");
|
||||
|
||||
/* 7. Reap child – must exit normally */
|
||||
if (waitpid(child, &status, 0) != child)
|
||||
ksft_exit_fail_msg("final waitpid");
|
||||
|
||||
if (!WIFEXITED(status)) {
|
||||
ksft_test_result_fail("child did not exit normally\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (WEXITSTATUS(status) != 0)
|
||||
ksft_test_result_pass("seccomp correctly denied modified syscall\n");
|
||||
else
|
||||
ksft_test_result_fail("write succeeded, orig_x0 bypass likely\n");
|
||||
|
||||
out:
|
||||
if (child > 0) {
|
||||
kill(child, SIGKILL);
|
||||
waitpid(child, NULL, 0);
|
||||
}
|
||||
ksft_print_cnts();
|
||||
return ksft_get_fail_cnt() ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -65,6 +65,9 @@
|
||||
/* VL 128..2048 in powers of 2 */
|
||||
#define MAX_NUM_VLS 5
|
||||
|
||||
/* Sentinel for detecting buffer bytes the kernel did not write */
|
||||
#define REGSET_SENTINEL 0xa5
|
||||
|
||||
/*
|
||||
* FPMR bits we can set without doing feature checks to see if values
|
||||
* are valid.
|
||||
@@ -181,6 +184,20 @@ static bool compare_buffer(const char *name, void *out,
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool buffer_is_filled(const void *buffer, size_t size,
|
||||
unsigned char value)
|
||||
{
|
||||
const unsigned char *bytes = buffer;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
if (bytes[i] != value)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct test_config {
|
||||
int sve_vl_in;
|
||||
int sve_vl_expected;
|
||||
@@ -401,6 +418,7 @@ static bool check_ptrace_values_sve(pid_t child, struct test_config *config)
|
||||
struct user_sve_header *sve;
|
||||
struct user_fpsimd_state *fpsimd;
|
||||
struct iovec iov;
|
||||
size_t buf_size;
|
||||
int ret, vq;
|
||||
bool pass = true;
|
||||
|
||||
@@ -409,14 +427,16 @@ static bool check_ptrace_values_sve(pid_t child, struct test_config *config)
|
||||
|
||||
vq = __sve_vq_from_vl(config->sve_vl_in);
|
||||
|
||||
iov.iov_len = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE);
|
||||
iov.iov_base = malloc(iov.iov_len);
|
||||
buf_size = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE);
|
||||
iov.iov_len = buf_size;
|
||||
iov.iov_base = malloc(buf_size);
|
||||
if (!iov.iov_base) {
|
||||
ksft_print_msg("OOM allocating %lu byte SVE buffer\n",
|
||||
iov.iov_len);
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(iov.iov_base, REGSET_SENTINEL, buf_size);
|
||||
ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_SVE, &iov);
|
||||
if (ret != 0) {
|
||||
ksft_print_msg("Failed to read initial SVE: %s (%d)\n",
|
||||
@@ -440,10 +460,16 @@ static bool check_ptrace_values_sve(pid_t child, struct test_config *config)
|
||||
}
|
||||
|
||||
if (svcr_in & SVCR_SM) {
|
||||
if (sve->size != sizeof(sve)) {
|
||||
if (sve->size != sizeof(*sve)) {
|
||||
ksft_print_msg("NT_ARM_SVE reports data with PSTATE.SM\n");
|
||||
pass = false;
|
||||
}
|
||||
if (!buffer_is_filled(iov.iov_base + sizeof(*sve),
|
||||
buf_size - sizeof(*sve), REGSET_SENTINEL)) {
|
||||
ksft_print_msg("NT_ARM_SVE wrote beyond its header with PSTATE.SM\n");
|
||||
pass = false;
|
||||
}
|
||||
goto out;
|
||||
} else {
|
||||
if (sve->size != SVE_PT_SIZE(vq, sve->flags)) {
|
||||
ksft_print_msg("Mismatch in SVE header size: %d != %lu\n",
|
||||
@@ -485,6 +511,7 @@ static bool check_ptrace_values_ssve(pid_t child, struct test_config *config)
|
||||
struct user_sve_header *sve;
|
||||
struct user_fpsimd_state *fpsimd;
|
||||
struct iovec iov;
|
||||
size_t buf_size;
|
||||
int ret, vq;
|
||||
bool pass = true;
|
||||
|
||||
@@ -493,14 +520,16 @@ static bool check_ptrace_values_ssve(pid_t child, struct test_config *config)
|
||||
|
||||
vq = __sve_vq_from_vl(config->sme_vl_in);
|
||||
|
||||
iov.iov_len = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE);
|
||||
iov.iov_base = malloc(iov.iov_len);
|
||||
buf_size = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE);
|
||||
iov.iov_len = buf_size;
|
||||
iov.iov_base = malloc(buf_size);
|
||||
if (!iov.iov_base) {
|
||||
ksft_print_msg("OOM allocating %lu byte SSVE buffer\n",
|
||||
iov.iov_len);
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(iov.iov_base, REGSET_SENTINEL, buf_size);
|
||||
ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_SSVE, &iov);
|
||||
if (ret != 0) {
|
||||
ksft_print_msg("Failed to read initial SSVE: %s (%d)\n",
|
||||
@@ -523,10 +552,16 @@ static bool check_ptrace_values_ssve(pid_t child, struct test_config *config)
|
||||
}
|
||||
|
||||
if (!(svcr_in & SVCR_SM)) {
|
||||
if (sve->size != sizeof(sve)) {
|
||||
if (sve->size != sizeof(*sve)) {
|
||||
ksft_print_msg("NT_ARM_SSVE reports data without PSTATE.SM\n");
|
||||
pass = false;
|
||||
}
|
||||
if (!buffer_is_filled(iov.iov_base + sizeof(*sve),
|
||||
buf_size - sizeof(*sve), REGSET_SENTINEL)) {
|
||||
ksft_print_msg("NT_ARM_SSVE wrote beyond its header without PSTATE.SM\n");
|
||||
pass = false;
|
||||
}
|
||||
goto out;
|
||||
} else {
|
||||
if (sve->size != SVE_PT_SIZE(vq, sve->flags)) {
|
||||
ksft_print_msg("Mismatch in SSVE header size: %d != %lu\n",
|
||||
|
||||
@@ -298,15 +298,20 @@ function irritator_handler
|
||||
add x0, x0, #1
|
||||
str x0, [x2, #ucontext_regs + 8 * 23]
|
||||
|
||||
#ifndef SSVE
|
||||
// Corrupt some random Z-regs
|
||||
movi v0.8b, #1
|
||||
movi v9.16b, #2
|
||||
movi v31.8b, #3
|
||||
// And P0
|
||||
ptrue p0.d
|
||||
#ifndef SSVE
|
||||
// And FFR
|
||||
wrffr p15.b
|
||||
#else
|
||||
// Enter and exit streaming mode, will reset all of the V, Z, P
|
||||
// and FFR registers that the system has.
|
||||
smstart_sm
|
||||
smstop
|
||||
#endif
|
||||
|
||||
ret
|
||||
|
||||
Reference in New Issue
Block a user