diff options
Diffstat (limited to 'tests')
-rwxr-xr-x | tests/functional/aarch64/test_device_passthrough.py | 4 | ||||
-rwxr-xr-x | tests/functional/aarch64/test_rme_sbsaref.py | 4 | ||||
-rwxr-xr-x | tests/functional/aarch64/test_rme_virt.py | 4 | ||||
-rw-r--r-- | tests/tcg/aarch64/Makefile.target | 5 | ||||
-rw-r--r-- | tests/tcg/aarch64/gcs.h | 80 | ||||
-rw-r--r-- | tests/tcg/aarch64/gcspushm.c | 71 | ||||
-rw-r--r-- | tests/tcg/aarch64/gcsss.c | 74 | ||||
-rw-r--r-- | tests/tcg/aarch64/gcsstr.c | 48 |
8 files changed, 284 insertions, 6 deletions
diff --git a/tests/functional/aarch64/test_device_passthrough.py b/tests/functional/aarch64/test_device_passthrough.py index 1743778..05a3f52 100755 --- a/tests/functional/aarch64/test_device_passthrough.py +++ b/tests/functional/aarch64/test_device_passthrough.py @@ -85,8 +85,8 @@ class Aarch64DevicePassthrough(QemuSystemTest): # https://docs.kernel.org/driver-api/vfio.html#vfio-device-cde ASSET_DEVICE_PASSTHROUGH_STACK = Asset( ('https://github.com/pbo-linaro/qemu-linux-stack/' - 'releases/download/build/device_passthrough-c3fb84a.tar.xz'), - '15ac2b02bed0c0ea8e3e007de0bcfdaf6fd51c1ba98213f841dc7d01d6f72f04') + 'releases/download/build/device_passthrough-a9612a2.tar.xz'), + 'f7d2f70912e7231986e6e293e1a2c4786dd02bec113a7acb6bfc619e96155455') # This tests the device passthrough implementation, by booting a VM # supporting it with two nvme disks attached, and launching a nested VM diff --git a/tests/functional/aarch64/test_rme_sbsaref.py b/tests/functional/aarch64/test_rme_sbsaref.py index ca892e0..6f92858 100755 --- a/tests/functional/aarch64/test_rme_sbsaref.py +++ b/tests/functional/aarch64/test_rme_sbsaref.py @@ -25,8 +25,8 @@ class Aarch64RMESbsaRefMachine(QemuSystemTest): # ./build.sh && ./archive_artifacts.sh out.tar.xz ASSET_RME_STACK_SBSA = Asset( ('https://github.com/pbo-linaro/qemu-linux-stack/' - 'releases/download/build/rme_sbsa_release-a7f02cf.tar.xz'), - '27d8400b11befb828d6db0cab97e7ae102d0992c928d3dfbf38b24b6cf6c324c') + 'releases/download/build/rme_sbsa_release-6a2dfc5.tar.xz'), + '5adba482aa069912292a8da746c6b21268224d9d81c97fe7c0bed690579ebdcb') # This tests the FEAT_RME cpu implementation, by booting a VM supporting it, # and launching a nested VM using it. diff --git a/tests/functional/aarch64/test_rme_virt.py b/tests/functional/aarch64/test_rme_virt.py index bb603aa..5e23773 100755 --- a/tests/functional/aarch64/test_rme_virt.py +++ b/tests/functional/aarch64/test_rme_virt.py @@ -23,8 +23,8 @@ class Aarch64RMEVirtMachine(QemuSystemTest): # ./build.sh && ./archive_artifacts.sh out.tar.xz ASSET_RME_STACK_VIRT = Asset( ('https://github.com/pbo-linaro/qemu-linux-stack/' - 'releases/download/build/rme_release-86101e5.tar.xz'), - 'e42fef8439badb52a071ac446fc33cff4cb7d61314c7a28fdbe61a11e1faad3a') + 'releases/download/build/rme_release-56bc99e.tar.xz'), + '0e3dc6b8a4b828dbae09c951a40dcb710eded084b32432b50c69cf4173ffa4be') # This tests the FEAT_RME cpu implementation, by booting a VM supporting it, # and launching a nested VM using it. diff --git a/tests/tcg/aarch64/Makefile.target b/tests/tcg/aarch64/Makefile.target index 1755874..55ce34e 100644 --- a/tests/tcg/aarch64/Makefile.target +++ b/tests/tcg/aarch64/Makefile.target @@ -75,6 +75,11 @@ AARCH64_TESTS += $(SME_TESTS) $(SME_TESTS): CFLAGS += $(CROSS_AS_HAS_ARMV9_SME) endif +# GCS Tests +GCS_TESTS += gcsstr gcspushm gcsss +AARCH64_TESTS += $(GCS_TESTS) +$(GCS_TESTS): gcs.h + # System Registers Tests AARCH64_TESTS += sysregs diff --git a/tests/tcg/aarch64/gcs.h b/tests/tcg/aarch64/gcs.h new file mode 100644 index 0000000..6f013d0 --- /dev/null +++ b/tests/tcg/aarch64/gcs.h @@ -0,0 +1,80 @@ +/* + * Linux kernel fallback API definitions for GCS and test helpers. + * + * Copyright (c) 2025 Linaro Ltd + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <sys/syscall.h> + +#ifndef PR_GET_SHADOW_STACK_STATUS +#define PR_GET_SHADOW_STACK_STATUS 74 +#endif +#ifndef PR_SET_SHADOW_STACK_STATUS +#define PR_SET_SHADOW_STACK_STATUS 75 +#endif +#ifndef PR_LOCK_SHADOW_STACK_STATUS +#define PR_LOCK_SHADOW_STACK_STATUS 76 +#endif +#ifndef PR_SHADOW_STACK_ENABLE +# define PR_SHADOW_STACK_ENABLE (1 << 0) +# define PR_SHADOW_STACK_WRITE (1 << 1) +# define PR_SHADOW_STACK_PUSH (1 << 2) +#endif +#ifndef SHADOW_STACK_SET_TOKEN +#define SHADOW_STACK_SET_TOKEN (1 << 0) +#endif +#ifndef SHADOW_STACK_SET_MARKER +#define SHADOW_STACK_SET_MARKER (1 << 1) +#endif +#ifndef SEGV_CPERR +#define SEGV_CPERR 10 +#endif +#ifndef __NR_map_shadow_stack +#define __NR_map_shadow_stack 453 +#endif + +/* + * Macros, and implement the syscall inline, lest we fail + * the checked return from any function call. + */ +#define enable_gcs(flags) \ + do { \ + register long num __asm__ ("x8") = __NR_prctl; \ + register long arg1 __asm__ ("x0") = PR_SET_SHADOW_STACK_STATUS; \ + register long arg2 __asm__ ("x1") = PR_SHADOW_STACK_ENABLE | flags; \ + register long arg3 __asm__ ("x2") = 0; \ + register long arg4 __asm__ ("x3") = 0; \ + register long arg5 __asm__ ("x4") = 0; \ + asm volatile("svc #0" \ + : "+r"(arg1) \ + : "r"(arg2), "r"(arg3), "r"(arg4), "r"(arg5), "r"(num) \ + : "memory", "cc"); \ + if (arg1) { \ + errno = -arg1; \ + perror("PR_SET_SHADOW_STACK_STATUS"); \ + exit(2); \ + } \ + } while (0) + +#define gcspr() \ + ({ uint64_t *r; asm volatile("mrs %0, s3_3_c2_c5_1" : "=r"(r)); r; }) + +#define gcsss1(val) \ + do { \ + asm volatile("sys #3, c7, c7, #2, %0" : : "r"(val) : "memory"); \ + } while (0) + +#define gcsss2() \ + ({ uint64_t *r; \ + asm volatile("sysl %0, #3, c7, c7, #3" : "=r"(r) : : "memory"); r; }) diff --git a/tests/tcg/aarch64/gcspushm.c b/tests/tcg/aarch64/gcspushm.c new file mode 100644 index 0000000..c330417 --- /dev/null +++ b/tests/tcg/aarch64/gcspushm.c @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gcs.h" + + +#define GCSPUSHM "sys #3, c7, c7, #0, %[push]" +#define GCSPOPM "sysl %[pop], #3, c7, c7, #1" + +static void test_sigsegv(int sig, siginfo_t *info, void *vuc) +{ + ucontext_t *uc = vuc; + uint64_t inst_sigsegv; + + __asm__("adr %0, inst_sigsegv" : "=r"(inst_sigsegv)); + assert(uc->uc_mcontext.pc == inst_sigsegv); + assert(info->si_code == SEGV_CPERR); + /* TODO: Dig for ESR and verify syndrome. */ + uc->uc_mcontext.pc += 4; +} + +static void test_sigill(int sig, siginfo_t *info, void *vuc) +{ + ucontext_t *uc = vuc; + uint64_t inst_sigill; + + __asm__("adr %0, inst_sigill" : "=r"(inst_sigill)); + assert(uc->uc_mcontext.pc == inst_sigill); + assert(info->si_code == ILL_ILLOPC); + uc->uc_mcontext.pc += 4; +} + +int main() +{ + struct sigaction sa = { .sa_flags = SA_SIGINFO }; + uint64_t old, new; + + sa.sa_sigaction = test_sigsegv; + if (sigaction(SIGSEGV, &sa, NULL) < 0) { + perror("sigaction"); + exit(1); + } + + sa.sa_sigaction = test_sigill; + if (sigaction(SIGILL, &sa, NULL) < 0) { + perror("sigaction"); + exit(1); + } + + /* Pushm is disabled -- SIGILL via EC_SYSTEMREGISTERTRAP */ + asm volatile("inst_sigill:\t" GCSPUSHM + : : [push] "r" (1)); + + enable_gcs(PR_SHADOW_STACK_PUSH); + + /* Valid value -- low 2 bits clear */ + old = 0xdeadbeeffeedcaec; + asm volatile(GCSPUSHM "\n\t" GCSPOPM + : [pop] "=r" (new) + : [push] "r" (old) + : "memory"); + assert(old == new); + + /* Invalid value -- SIGSEGV via EC_GCS */ + asm volatile(GCSPUSHM "\n" + "inst_sigsegv:\t" GCSPOPM + : [pop] "=r" (new) + : [push] "r" (1) + : "memory"); + + exit(0); +} diff --git a/tests/tcg/aarch64/gcsss.c b/tests/tcg/aarch64/gcsss.c new file mode 100644 index 0000000..9550c68 --- /dev/null +++ b/tests/tcg/aarch64/gcsss.c @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gcs.h" + +#define IN_PROGRESS(X) ((uint64_t)(X) | 5) +#define CAP(X) (((uint64_t)(X) & ~0xfff) + 1) + +static uint64_t * __attribute__((noinline)) recurse(size_t index) +{ + if (index == 0) { + return gcspr(); + } + return recurse(index - 1); +} + +int main() +{ + void *tmp; + uint64_t *alt_stack, *alt_cap; + uint64_t *orig_pr, *orig_cap; + uint64_t *bottom; + size_t pagesize = getpagesize(); + size_t words; + + enable_gcs(0); + orig_pr = gcspr(); + + /* Allocate a guard page before and after. */ + tmp = mmap(0, 3 * pagesize, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0); + assert(tmp != MAP_FAILED); + + /* map_shadow_stack won't replace existing mappings */ + munmap(tmp + pagesize, pagesize); + + /* Allocate a new stack between the guards. */ + alt_stack = (uint64_t *) + syscall(__NR_map_shadow_stack, tmp + pagesize, pagesize, + SHADOW_STACK_SET_TOKEN); + assert(alt_stack == tmp + pagesize); + + words = pagesize / 8; + alt_cap = alt_stack + words - 1; + + /* SHADOW_STACK_SET_TOKEN set the cap. */ + assert(*alt_cap == CAP(alt_cap)); + + /* Swap to the alt stack, one step at a time. */ + gcsss1(alt_cap); + + assert(gcspr() == alt_cap); + assert(*alt_cap == IN_PROGRESS(orig_pr)); + + orig_cap = gcsss2(); + + assert(orig_cap == orig_pr - 1); + assert(*orig_cap == CAP(orig_cap)); + assert(gcspr() == alt_stack + words); + + /* We should be able to use the whole stack. */ + bottom = recurse(words - 1); + assert(bottom == alt_stack); + + /* We should be back where we started. */ + assert(gcspr() == alt_stack + words); + + /* Swap back to the original stack. */ + gcsss1(orig_cap); + tmp = gcsss2(); + + assert(gcspr() == orig_pr); + assert(tmp == alt_cap); + + exit(0); +} diff --git a/tests/tcg/aarch64/gcsstr.c b/tests/tcg/aarch64/gcsstr.c new file mode 100644 index 0000000..b045aee --- /dev/null +++ b/tests/tcg/aarch64/gcsstr.c @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gcs.h" + +/* + * A single garbage store to the gcs stack. + * The asm inside must be unique, so disallow inlining. + */ +void __attribute__((noinline)) +test_gcsstr(void) +{ + register uint64_t *ptr __asm__("x0") = gcspr(); + /* GCSSTR x1, x0 */ + __asm__("inst_gcsstr: .inst 0xd91f1c01" : : "r"(--ptr)); +} + +static void test_sigsegv(int sig, siginfo_t *info, void *vuc) +{ + ucontext_t *uc = vuc; + uint64_t inst_gcsstr; + + __asm__("adr %0, inst_gcsstr" : "=r"(inst_gcsstr)); + assert(uc->uc_mcontext.pc == inst_gcsstr); + assert(info->si_code == SEGV_CPERR); + /* TODO: Dig for ESR and verify syndrome. */ + exit(0); +} + +int main() +{ + struct sigaction sa = { + .sa_sigaction = test_sigsegv, + .sa_flags = SA_SIGINFO, + }; + + /* Enable GCSSTR and test the store succeeds. */ + enable_gcs(PR_SHADOW_STACK_WRITE); + test_gcsstr(); + + /* Disable GCSSTR and test the resulting sigsegv. */ + enable_gcs(0); + if (sigaction(SIGSEGV, &sa, NULL) < 0) { + perror("sigaction"); + exit(1); + } + test_gcsstr(); + abort(); +} |