diff options
Diffstat (limited to 'tools/test/stress2/misc/pkru.sh')
-rwxr-xr-x | tools/test/stress2/misc/pkru.sh | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/tools/test/stress2/misc/pkru.sh b/tools/test/stress2/misc/pkru.sh new file mode 100755 index 000000000000..9ac0a5000daf --- /dev/null +++ b/tools/test/stress2/misc/pkru.sh @@ -0,0 +1,512 @@ +#/bin/sh +# +# SPDX-License-Identifier: BSD-2-Clause-FreeBSD +# +# Copyright (c) 2019 Konstantin Belousov <kib@FreeBSD.org> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# + +# Test scenario for Intel userspace protection keys feature on Skylake Xeons + +grep -qw PKU /var/run/dmesg.boot || exit 0 +cd /tmp +cat > /tmp/pkru_exec.c <<EOF +/* $Id: pkru_exec.c,v 1.4 2019/01/12 04:55:57 kostik Exp kostik $ */ +/* + * cc -Wall -Wextra -g -O -o pkru_fork64 pkru_fork.c + * Run it with LD_BIND_NOW=1. + */ + +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <machine/sysarch.h> +#include <err.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef TEST_COMPILE +int x86_pkru_get_perm(unsigned int keyidx, int *access, int *modify); +int x86_pkru_set_perm(unsigned int keyidx, int access, int modify); +int x86_pkru_protect_range(void *addr, unsigned long len, unsigned int keyidx, + int flag); +int x86_pkru_unprotect_range(void *addr, unsigned long len); +uint32_t rdpkru(void); +void wrpkru(uint32_t); +#define AMD64_PKRU_PERSIST 0x0001 +#endif + +extern char **environ; + +#define OPKEY 1 + +int +main(void) +{ + char *args[3] = { + "/bin/date", + NULL + }; + struct rlimit rl; + + if (getrlimit(RLIMIT_STACK, &rl) != 0) + err(1, "getrlimit RLIMIT_STACK"); + if (x86_pkru_protect_range(0, 0x800000000000 - rl.rlim_max, OPKEY, + AMD64_PKRU_PERSIST) != 0) + err(1, "x86_pkru_protect_range"); + if (x86_pkru_set_perm(1, 1, 0) != 0) + err(1, "x86_pkru_set_perm"); + execve("/bin/date", args, environ); +} +EOF +cc -Wall -Wextra -g -O -o pkru_exec64 pkru_exec.c || exit 1 +cc -Wall -Wextra -g -O -o pkru_exec32 pkru_exec.c -m32 || exit 1 +rm pkru_exec.c +echo "Expect: Segmentation fault (core dumped)" +LD_BIND_NOW=1 ./pkru_exec64 +LD_BIND_NOW=1 ./pkru_exec32 +rm -f pkru_exec64 pkru_exec32 pkru_exec64.core pkru_exec32.core + +cat > /tmp/pkru_fork.c <<EOF +/* $Id: pkru_fork.c,v 1.2 2019/01/12 03:39:42 kostik Exp kostik $ */ +/* cc -Wall -Wextra -g -O -o pkru_fork64 pkru_fork.c */ + +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/wait.h> +#include <machine/cpufunc.h> +#include <machine/sysarch.h> +#include <x86/fpu.h> +#include <err.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef TEST_COMPILE +int x86_pkru_get_perm(unsigned int keyidx, int *access, int *modify); +int x86_pkru_set_perm(unsigned int keyidx, int access, int modify); +int x86_pkru_protect_range(void *addr, unsigned long len, unsigned int keyidx, + int flag); +int x86_pkru_unprotect_range(void *addr, unsigned long len); +uint32_t rdpkru(void); +void wrpkru(uint32_t); +#endif + +static volatile char *mapping; + +#define OPKEY 1 + +int +main(void) +{ + int error; + pid_t child; + + mapping = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (mapping == MAP_FAILED) + err(1, "mmap"); + error = x86_pkru_protect_range((void *)mapping, getpagesize(), + OPKEY, 0); + if (error != 0) + err(1, "x86_pkru_protect_range"); + error = x86_pkru_set_perm(OPKEY, 0, 0); + if (error != 0) + err(1, "x86_pkru_set_perm"); + child = fork(); + if (child == -1) + err(1, "fork"); + if (child == 0) { + *mapping = 0; + printf("Still alive, pkru did not worked after fork"); + } + waitpid(child, NULL, 0); +} +EOF +cc -Wall -Wextra -g -O -o pkru_fork64 pkru_fork.c || exit 1 +cc -Wall -Wextra -g -O -o pkru_fork32 -m32 pkru_fork.c || exit 1 +rm pkru_fork.c +./pkru_fork64 +./pkru_fork32 +rm -f pkru_fork64 pkru_fork64.core pkru_fork32 pkru_fork32.core + +cat > /tmp/pkru_perm.c <<EOF +/* $Id: pkru_perm.c,v 1.6 2019/01/12 04:43:20 kostik Exp kostik $ */ +/* cc -Wall -Wextra -g -O -o pkru_fork64 pkru_fork.c */ + +#include <sys/types.h> +#include <sys/mman.h> +#include <machine/cpufunc.h> +#include <machine/sysarch.h> +#include <x86/fpu.h> +#include <err.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef TEST_COMPILE +int x86_pkru_get_perm(unsigned int keyidx, int *access, int *modify); +int x86_pkru_set_perm(unsigned int keyidx, int access, int modify); +int x86_pkru_protect_range(void *addr, unsigned long len, unsigned int keyidx, + int flag); +int x86_pkru_unprotect_range(void *addr, unsigned long len); +uint32_t rdpkru(void); +void wrpkru(uint32_t); +#define AMD64_PKRU_PERSIST 0x0001 +#endif + +static void +sighandler(int signo __unused, siginfo_t *si __unused, void *uc1 __unused) +{ + + exit(0); +} + +static volatile char *mapping; + +#define OPKEY 1 + +int +main(void) +{ + struct sigaction sa; + char *mapping1; + int error; + + error = x86_pkru_set_perm(OPKEY, 0, 0); + if (error != 0) + err(1, "x86_pkru_set_perm"); + mapping = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (mapping == MAP_FAILED) + err(1, "mmap"); + error = x86_pkru_protect_range((void *)mapping, getpagesize(), + OPKEY, 0); + if (error != 0) + err(1, "x86_pkru_protect_range"); + error = munmap((void *)mapping, getpagesize()); + if (error != 0) + err(1, "munmap"); + mapping1 = mmap((void *)mapping, getpagesize(), PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED | MAP_EXCL, -1, 0); + if (mapping1 == MAP_FAILED) + err(1, "mmap 2"); + *mapping = 0; + error = x86_pkru_protect_range((void *)mapping, getpagesize(), + OPKEY, AMD64_PKRU_PERSIST); + mapping1 = mmap((void *)mapping, getpagesize(), PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + if (mapping1 == MAP_FAILED) + err(1, "mmap 3"); + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = sighandler; + sa.sa_flags = SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL) == -1) + err(1, "sigaction"); + *mapping = 0; + printf("Still alive, pkru persist did not worked"); + exit(1); +} +EOF +cc -Wall -Wextra -g -O -o pkru_perm64 pkru_perm.c || exit 1 +cc -Wall -Wextra -g -O -o pkru_perm32 -m32 pkru_perm.c || exit 1 +rm pkru_perm.c +./pkru_perm64 +./pkru_perm32 +rm -f pkru_perm64 pkru_perm32 + +cat > /tmp/pkru.c <<EOF +/* $Id: pkru.c,v 1.27 2019/01/10 12:06:31 kostik Exp $ */ +/* cc -Wall -Wextra -g -O -o pkru64 pkru.c -lpthread */ + +#include <sys/types.h> +#include <sys/mman.h> +#include <machine/cpufunc.h> +#include <machine/sysarch.h> +#include <x86/fpu.h> +#include <err.h> +#include <pthread.h> +#include <pthread_np.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef TEST_COMPILE +int x86_pkru_get_perm(unsigned int keyidx, int *access, int *modify); +int x86_pkru_set_perm(unsigned int keyidx, int access, int modify); +int x86_pkru_protect_range(void *addr, unsigned long len, unsigned int keyidx, + int flag); +int x86_pkru_unprotect_range(void *addr, unsigned long len); +uint32_t rdpkru(void); +void wrpkru(uint32_t); +#endif + +static char *mut_region; +static size_t mut_region_len; +static unsigned *mut_region_keys; +static pthread_t bga_thr; +static int signal_seen; +static siginfo_t si_seen; +static ucontext_t *uc_seen; +static u_int rpku_offset; + +static void +handler(int i __unused) { + _exit(0); +} + +static void +report_sig(int signo, siginfo_t *si, ucontext_t *uc) +{ + + printf("signal %d %s", signo, strsignal(signo)); + printf(" si_code %d si_status %d si_addr %p", si->si_code, + si->si_status, si->si_addr); + printf(" mc_err %#jx", (uintmax_t)uc->uc_mcontext.mc_err); + if (uc->uc_mcontext.mc_xfpustate != 0 && + (unsigned long)uc->uc_mcontext.mc_xfpustate_len >= + rpku_offset) { + printf(" pkru 0x%08x", *(uint32_t *)( + uc->uc_mcontext.mc_xfpustate + rpku_offset)); + } + printf("\n"); +} + +static void +sighandler(int signo, siginfo_t *si, void *u) +{ + ucontext_t *uc; + pthread_t thr; + size_t len; + uint32_t *pkrup; + + uc = u; + thr = pthread_self(); + if (thr == bga_thr) { + printf("Fault from background access thread\n"); + report_sig(signo, si, uc); + exit(1); + } + signal_seen = signo; + si_seen = *si; + + len = sizeof(ucontext_t); + if (uc->uc_mcontext.mc_xfpustate != 0) + len += uc->uc_mcontext.mc_xfpustate_len; + uc_seen = malloc(len); + if (uc_seen == NULL) + err(1, "malloc(%d)", (int)len); + memcpy(uc_seen, uc, sizeof(*uc)); +#if 0 +printf("signal %d xpfustate %p len %ld rpkuo %u\n", signo, (void *)uc->uc_mcontext.mc_xfpustate, uc->uc_mcontext.mc_xfpustate_len, rpku_offset); +#endif + if (uc->uc_mcontext.mc_xfpustate != 0) { + uc_seen->uc_mcontext.mc_xfpustate = (uintptr_t)uc_seen + + sizeof(*uc); + memcpy((void *)uc_seen->uc_mcontext.mc_xfpustate, + (void *)uc->uc_mcontext.mc_xfpustate, + uc->uc_mcontext.mc_xfpustate_len); + + if ((unsigned long)uc->uc_mcontext.mc_xfpustate_len >= + rpku_offset + sizeof(uint32_t)) { + pkrup = (uint32_t *)(rpku_offset + + (char *)uc->uc_mcontext.mc_xfpustate); +#if 0 +printf("signal %d *pkru %08x\n", signo, *pkrup); +#endif + *pkrup = 0; + } + } +} + +static void * +bg_access_thread_fn(void *arg __unused) +{ + char *c, x; + + pthread_set_name_np(pthread_self(), "bgaccess"); + for (x = 0, c = mut_region;;) { + *c = x; + if (++c >= mut_region + mut_region_len) { + c = mut_region; + x++; + } + } + return (NULL); +} + +static void +clear_signal_report(void) +{ + + signal_seen = 0; + free(uc_seen); + uc_seen = NULL; +} + +static void +check_signal(unsigned key, int check_access, int check_modify) +{ + + if (signal_seen == 0) { + printf("Did not get signal, key %d check_access %d " + "check_modify %d\n", key, check_access, check_modify); + printf("pkru 0x%08x\n", rdpkru()); + exit(1); + } +} + +static void +check_no_signal(void) +{ + + if (signal_seen != 0) { + printf("pkru 0x%08x\n", rdpkru()); + printf("Got signal\n"); + report_sig(signal_seen, &si_seen, uc_seen); + exit(1); + } +} + +static void +check(char *p, unsigned key, int check_access, int check_modify) +{ + int access, error, modify, orig_access, orig_modify; + + error = x86_pkru_get_perm(key, &orig_access, &orig_modify); + if (error != 0) + err(1, "x86_pkru_get_perm"); + access = check_access ? 0 : 1; + modify = check_modify ? 0 : 1; + error = x86_pkru_set_perm(key, access, modify); + if (error != 0) + err(1, "x86_pkru_set_perm access"); + clear_signal_report(); + if (check_modify) + *(volatile char *)p = 1; + else if (check_access) + *(volatile char *)p; + if (key == mut_region_keys[(p - mut_region) / getpagesize()]) + check_signal(key, check_access, check_modify); + else + check_no_signal(); + error = x86_pkru_set_perm(key, orig_access, orig_modify); + if (error != 0) + err(1, "x86_pkru_set_perm access restore"); + clear_signal_report(); + if (check_modify) + *(volatile char *)p = 1; + else if (check_access) + *(volatile char *)p; + check_no_signal(); +} + +static void +mutate_perms(void) +{ + unsigned key; + char *p; + + for (p = mut_region;;) { + for (key = 1; key < 0x10; key++) { + check(p, key, 1, 0); + check(p, key, 0, 1); + check(p, key, 1, 1); + } + p += getpagesize(); + if (p >= mut_region + mut_region_len) + p = mut_region; + } +} + +int +main(void) +{ + struct sigaction sa; + char *p; + unsigned i; + u_int regs[4]; + int error; + + cpuid_count(0xd, 0x9, regs); + rpku_offset = regs[1]; + if (rpku_offset != 0) +#if defined(__i386__) + rpku_offset -= sizeof(union savefpu); +#else + rpku_offset -= sizeof(struct savefpu); +#endif + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = sighandler; + sa.sa_flags = SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL) == -1) + err(1, "sigaction SIGSEGV"); + if (sigaction(SIGBUS, &sa, NULL) == -1) + err(1, "sigaction SIGBUS"); + + mut_region_len = getpagesize() * 100; + mut_region_keys = calloc(mut_region_len, sizeof(unsigned)); + if (mut_region_keys == NULL) + err(1, "calloc keys"); + mut_region = mmap(NULL, mut_region_len, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_SHARED, -1, 0); + if (mut_region == MAP_FAILED) + err(1, "mmap"); + for (i = 1, p = mut_region; p < mut_region + mut_region_len; + p += getpagesize()) { + error = x86_pkru_protect_range(p, getpagesize(), i, 0); + if (error != 0) + err(1, "x86_pkru_protect_range key %d", i); + mut_region_keys[(p - mut_region) / getpagesize()] = i; + if (++i > 0xf) + i = 1; + } + + signal(SIGALRM, handler); + alarm(5); + error = pthread_create(&bga_thr, NULL, bg_access_thread_fn, NULL); + if (error != 0) + errc(1, error, "pthread create background access thread"); + + mutate_perms(); +} +EOF +cc -Wall -Wextra -g -O -o pkru64 pkru.c -lpthread || exit 1 +cc -Wall -Wextra -g -O -o pkru32 -m32 pkru.c -lpthread || exit 1 +rm pkru.c +./pkru64 +./pkru32 +rm -f pkru64 pkru32 + +exit |