]> git.itanic.dy.fi Git - linux-stable/commitdiff
selftests/vm/pkeys: Add a regression test for setting PKRU through ptrace
authorKyle Huey <me@kylehuey.com>
Mon, 9 Jan 2023 21:02:14 +0000 (13:02 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 14 Jan 2023 09:23:28 +0000 (10:23 +0100)
commit 6ea25770b043c7997ab21d1ce95ba5de4d3d85d9 upstream

This tests PTRACE_SETREGSET with NT_X86_XSTATE modifying PKRU directly and
removing the PKRU bit from XSTATE_BV.

Signed-off-by: Kyle Huey <me@kylehuey.com>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Link: https://lore.kernel.org/all/20221115230932.7126-7-khuey%40kylehuey.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
tools/testing/selftests/vm/pkey-x86.h
tools/testing/selftests/vm/protection_keys.c

index e4a4ce2b826d27a00a3a89d781aff1da1d72c0a7..ea8c8afbcdbb39d34e83aa15fd906db3b10fd58b 100644 (file)
@@ -119,6 +119,18 @@ static inline int cpu_has_pkeys(void)
        return 1;
 }
 
+static inline int cpu_max_xsave_size(void)
+{
+       unsigned long XSTATE_CPUID = 0xd;
+       unsigned int eax;
+       unsigned int ebx;
+       unsigned int ecx;
+       unsigned int edx;
+
+       __cpuid_count(XSTATE_CPUID, 0, eax, ebx, ecx, edx);
+       return ecx;
+}
+
 static inline u32 pkey_bit_position(int pkey)
 {
        return pkey * PKEY_BITS_PER_PKEY;
index 2d0ae88665db09fb56ebe25bf07cbe7fabc4e254..2d48272b2463e44fbab2690258380402426c0dbc 100644 (file)
  *     do a plain mprotect() to a mprotect_pkey() area and make sure the pkey sticks
  *
  * Compile like this:
- *     gcc      -o protection_keys    -O2 -g -std=gnu99 -pthread -Wall protection_keys.c -lrt -ldl -lm
- *     gcc -m32 -o protection_keys_32 -O2 -g -std=gnu99 -pthread -Wall protection_keys.c -lrt -ldl -lm
+ *     gcc -mxsave      -o protection_keys    -O2 -g -std=gnu99 -pthread -Wall protection_keys.c -lrt -ldl -lm
+ *     gcc -mxsave -m32 -o protection_keys_32 -O2 -g -std=gnu99 -pthread -Wall protection_keys.c -lrt -ldl -lm
  */
 #define _GNU_SOURCE
 #define __SANE_USERSPACE_TYPES__
 #include <errno.h>
+#include <linux/elf.h>
 #include <linux/futex.h>
 #include <time.h>
 #include <sys/time.h>
@@ -1550,6 +1551,129 @@ void test_implicit_mprotect_exec_only_memory(int *ptr, u16 pkey)
        do_not_expect_pkey_fault("plain read on recently PROT_EXEC area");
 }
 
+#if defined(__i386__) || defined(__x86_64__)
+void test_ptrace_modifies_pkru(int *ptr, u16 pkey)
+{
+       u32 new_pkru;
+       pid_t child;
+       int status, ret;
+       int pkey_offset = pkey_reg_xstate_offset();
+       size_t xsave_size = cpu_max_xsave_size();
+       void *xsave;
+       u32 *pkey_register;
+       u64 *xstate_bv;
+       struct iovec iov;
+
+       new_pkru = ~read_pkey_reg();
+       /* Don't make PROT_EXEC mappings inaccessible */
+       new_pkru &= ~3;
+
+       child = fork();
+       pkey_assert(child >= 0);
+       dprintf3("[%d] fork() ret: %d\n", getpid(), child);
+       if (!child) {
+               ptrace(PTRACE_TRACEME, 0, 0, 0);
+               /* Stop and allow the tracer to modify PKRU directly */
+               raise(SIGSTOP);
+
+               /*
+                * need __read_pkey_reg() version so we do not do shadow_pkey_reg
+                * checking
+                */
+               if (__read_pkey_reg() != new_pkru)
+                       exit(1);
+
+               /* Stop and allow the tracer to clear XSTATE_BV for PKRU */
+               raise(SIGSTOP);
+
+               if (__read_pkey_reg() != 0)
+                       exit(1);
+
+               /* Stop and allow the tracer to examine PKRU */
+               raise(SIGSTOP);
+
+               exit(0);
+       }
+
+       pkey_assert(child == waitpid(child, &status, 0));
+       dprintf3("[%d] waitpid(%d) status: %x\n", getpid(), child, status);
+       pkey_assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
+
+       xsave = (void *)malloc(xsave_size);
+       pkey_assert(xsave > 0);
+
+       /* Modify the PKRU register directly */
+       iov.iov_base = xsave;
+       iov.iov_len = xsave_size;
+       ret = ptrace(PTRACE_GETREGSET, child, (void *)NT_X86_XSTATE, &iov);
+       pkey_assert(ret == 0);
+
+       pkey_register = (u32 *)(xsave + pkey_offset);
+       pkey_assert(*pkey_register == read_pkey_reg());
+
+       *pkey_register = new_pkru;
+
+       ret = ptrace(PTRACE_SETREGSET, child, (void *)NT_X86_XSTATE, &iov);
+       pkey_assert(ret == 0);
+
+       /* Test that the modification is visible in ptrace before any execution */
+       memset(xsave, 0xCC, xsave_size);
+       ret = ptrace(PTRACE_GETREGSET, child, (void *)NT_X86_XSTATE, &iov);
+       pkey_assert(ret == 0);
+       pkey_assert(*pkey_register == new_pkru);
+
+       /* Execute the tracee */
+       ret = ptrace(PTRACE_CONT, child, 0, 0);
+       pkey_assert(ret == 0);
+
+       /* Test that the tracee saw the PKRU value change */
+       pkey_assert(child == waitpid(child, &status, 0));
+       dprintf3("[%d] waitpid(%d) status: %x\n", getpid(), child, status);
+       pkey_assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
+
+       /* Test that the modification is visible in ptrace after execution */
+       memset(xsave, 0xCC, xsave_size);
+       ret = ptrace(PTRACE_GETREGSET, child, (void *)NT_X86_XSTATE, &iov);
+       pkey_assert(ret == 0);
+       pkey_assert(*pkey_register == new_pkru);
+
+       /* Clear the PKRU bit from XSTATE_BV */
+       xstate_bv = (u64 *)(xsave + 512);
+       *xstate_bv &= ~(1 << 9);
+
+       ret = ptrace(PTRACE_SETREGSET, child, (void *)NT_X86_XSTATE, &iov);
+       pkey_assert(ret == 0);
+
+       /* Test that the modification is visible in ptrace before any execution */
+       memset(xsave, 0xCC, xsave_size);
+       ret = ptrace(PTRACE_GETREGSET, child, (void *)NT_X86_XSTATE, &iov);
+       pkey_assert(ret == 0);
+       pkey_assert(*pkey_register == 0);
+
+       ret = ptrace(PTRACE_CONT, child, 0, 0);
+       pkey_assert(ret == 0);
+
+       /* Test that the tracee saw the PKRU value go to 0 */
+       pkey_assert(child == waitpid(child, &status, 0));
+       dprintf3("[%d] waitpid(%d) status: %x\n", getpid(), child, status);
+       pkey_assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
+
+       /* Test that the modification is visible in ptrace after execution */
+       memset(xsave, 0xCC, xsave_size);
+       ret = ptrace(PTRACE_GETREGSET, child, (void *)NT_X86_XSTATE, &iov);
+       pkey_assert(ret == 0);
+       pkey_assert(*pkey_register == 0);
+
+       ret = ptrace(PTRACE_CONT, child, 0, 0);
+       pkey_assert(ret == 0);
+       pkey_assert(child == waitpid(child, &status, 0));
+       dprintf3("[%d] waitpid(%d) status: %x\n", getpid(), child, status);
+       pkey_assert(WIFEXITED(status));
+       pkey_assert(WEXITSTATUS(status) == 0);
+       free(xsave);
+}
+#endif
+
 void test_mprotect_pkey_on_unsupported_cpu(int *ptr, u16 pkey)
 {
        int size = PAGE_SIZE;
@@ -1585,6 +1709,9 @@ void (*pkey_tests[])(int *ptr, u16 pkey) = {
        test_pkey_syscalls_bad_args,
        test_pkey_alloc_exhaust,
        test_pkey_alloc_free_attach_pkey0,
+#if defined(__i386__) || defined(__x86_64__)
+       test_ptrace_modifies_pkru,
+#endif
 };
 
 void run_tests_once(void)