Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

selftests/mm: Add new testcases for pkeys

Add a few new tests to exercise the signal handler flow, especially with
PKEY 0 disabled:

- Verify that the SIGSEGV handler is invoked when pkey 0 is disabled.

- Verify that a thread which disables PKEY 0 segfaults with PKUERR when
accessing the stack.

- Verify that the SIGSEGV handler that uses an alternate signal stack is
correctly invoked when the thread disabled PKEY 0

- Verify that the PKRU value set by the application is correctly restored
upon return from signal handling.

- Verify that sigreturn() is able to restore the altstack even if the
thread had PKEY 0 disabled

[ Aruna: Adapted to upstream ]
[ tglx: Made it actually compile. Restored protection_keys compile. Added
useful info to the changelog instead of bare function names. ]

Signed-off-by: Keith Lucas <keith.lucas@oracle.com>
Signed-off-by: Aruna Ramakrishna <aruna.ramakrishna@oracle.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/all/20240802061318.2140081-6-aruna.ramakrishna@oracle.com

authored by

Keith Lucas and committed by
Thomas Gleixner
6998a73e d10b5549

+494 -11
+1
tools/testing/selftests/mm/Makefile
··· 88 88 CAN_BUILD_WITH_NOPIE := $(shell ./../x86/check_cc.sh "$(CC)" ../x86/trivial_program.c -no-pie) 89 89 90 90 VMTARGETS := protection_keys 91 + VMTARGETS += pkey_sighandler_tests 91 92 BINARIES_32 := $(VMTARGETS:%=%_32) 92 93 BINARIES_64 := $(VMTARGETS:%=%_64) 93 94
+12 -1
tools/testing/selftests/mm/pkey-helpers.h
··· 79 79 } \ 80 80 } while (0) 81 81 82 - __attribute__((noinline)) int read_ptr(int *ptr); 82 + #define barrier() __asm__ __volatile__("": : :"memory") 83 + #ifndef noinline 84 + # define noinline __attribute__((noinline)) 85 + #endif 86 + 87 + noinline int read_ptr(int *ptr) 88 + { 89 + /* Keep GCC from optimizing this away somehow */ 90 + barrier(); 91 + return *ptr; 92 + } 93 + 83 94 void expected_pkey_fault(int pkey); 84 95 int sys_pkey_alloc(unsigned long flags, unsigned long init_val); 85 96 int sys_pkey_free(unsigned long pkey);
+481
tools/testing/selftests/mm/pkey_sighandler_tests.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Tests Memory Protection Keys (see Documentation/core-api/protection-keys.rst) 4 + * 5 + * The testcases in this file exercise various flows related to signal handling, 6 + * using an alternate signal stack, with the default pkey (pkey 0) disabled. 7 + * 8 + * Compile with: 9 + * gcc -mxsave -o pkey_sighandler_tests -O2 -g -std=gnu99 -pthread -Wall pkey_sighandler_tests.c -I../../../../tools/include -lrt -ldl -lm 10 + * gcc -mxsave -m32 -o pkey_sighandler_tests -O2 -g -std=gnu99 -pthread -Wall pkey_sighandler_tests.c -I../../../../tools/include -lrt -ldl -lm 11 + */ 12 + #define _GNU_SOURCE 13 + #define __SANE_USERSPACE_TYPES__ 14 + #include <errno.h> 15 + #include <sys/syscall.h> 16 + #include <string.h> 17 + #include <stdio.h> 18 + #include <stdint.h> 19 + #include <stdbool.h> 20 + #include <signal.h> 21 + #include <assert.h> 22 + #include <stdlib.h> 23 + #include <sys/mman.h> 24 + #include <sys/types.h> 25 + #include <sys/stat.h> 26 + #include <unistd.h> 27 + #include <pthread.h> 28 + #include <limits.h> 29 + 30 + #include "pkey-helpers.h" 31 + 32 + #define STACK_SIZE PTHREAD_STACK_MIN 33 + 34 + void expected_pkey_fault(int pkey) {} 35 + 36 + pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 37 + pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 38 + siginfo_t siginfo = {0}; 39 + 40 + /* 41 + * We need to use inline assembly instead of glibc's syscall because glibc's 42 + * syscall will attempt to access the PLT in order to call a library function 43 + * which is protected by MPK 0 which we don't have access to. 44 + */ 45 + static inline __always_inline 46 + long syscall_raw(long n, long a1, long a2, long a3, long a4, long a5, long a6) 47 + { 48 + unsigned long ret; 49 + #ifdef __x86_64__ 50 + register long r10 asm("r10") = a4; 51 + register long r8 asm("r8") = a5; 52 + register long r9 asm("r9") = a6; 53 + asm volatile ("syscall" 54 + : "=a"(ret) 55 + : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8), "r"(r9) 56 + : "rcx", "r11", "memory"); 57 + #elif defined __i386__ 58 + asm volatile ("int $0x80" 59 + : "=a"(ret) 60 + : "a"(n), "b"(a1), "c"(a2), "d"(a3), "S"(a4), "D"(a5) 61 + : "memory"); 62 + #else 63 + # error syscall_raw() not implemented 64 + #endif 65 + return ret; 66 + } 67 + 68 + static void sigsegv_handler(int signo, siginfo_t *info, void *ucontext) 69 + { 70 + pthread_mutex_lock(&mutex); 71 + 72 + memcpy(&siginfo, info, sizeof(siginfo_t)); 73 + 74 + pthread_cond_signal(&cond); 75 + pthread_mutex_unlock(&mutex); 76 + 77 + syscall_raw(SYS_exit, 0, 0, 0, 0, 0, 0); 78 + } 79 + 80 + static void sigusr1_handler(int signo, siginfo_t *info, void *ucontext) 81 + { 82 + pthread_mutex_lock(&mutex); 83 + 84 + memcpy(&siginfo, info, sizeof(siginfo_t)); 85 + 86 + pthread_cond_signal(&cond); 87 + pthread_mutex_unlock(&mutex); 88 + } 89 + 90 + static void sigusr2_handler(int signo, siginfo_t *info, void *ucontext) 91 + { 92 + /* 93 + * pkru should be the init_pkru value which enabled MPK 0 so 94 + * we can use library functions. 95 + */ 96 + printf("%s invoked.\n", __func__); 97 + } 98 + 99 + static void raise_sigusr2(void) 100 + { 101 + pid_t tid = 0; 102 + 103 + tid = syscall_raw(SYS_gettid, 0, 0, 0, 0, 0, 0); 104 + 105 + syscall_raw(SYS_tkill, tid, SIGUSR2, 0, 0, 0, 0); 106 + 107 + /* 108 + * We should return from the signal handler here and be able to 109 + * return to the interrupted thread. 110 + */ 111 + } 112 + 113 + static void *thread_segv_with_pkey0_disabled(void *ptr) 114 + { 115 + /* Disable MPK 0 (and all others too) */ 116 + __write_pkey_reg(0x55555555); 117 + 118 + /* Segfault (with SEGV_MAPERR) */ 119 + *(int *) (0x1) = 1; 120 + return NULL; 121 + } 122 + 123 + static void *thread_segv_pkuerr_stack(void *ptr) 124 + { 125 + /* Disable MPK 0 (and all others too) */ 126 + __write_pkey_reg(0x55555555); 127 + 128 + /* After we disable MPK 0, we can't access the stack to return */ 129 + return NULL; 130 + } 131 + 132 + static void *thread_segv_maperr_ptr(void *ptr) 133 + { 134 + stack_t *stack = ptr; 135 + int *bad = (int *)1; 136 + 137 + /* 138 + * Setup alternate signal stack, which should be pkey_mprotect()ed by 139 + * MPK 0. The thread's stack cannot be used for signals because it is 140 + * not accessible by the default init_pkru value of 0x55555554. 141 + */ 142 + syscall_raw(SYS_sigaltstack, (long)stack, 0, 0, 0, 0, 0); 143 + 144 + /* Disable MPK 0. Only MPK 1 is enabled. */ 145 + __write_pkey_reg(0x55555551); 146 + 147 + /* Segfault */ 148 + *bad = 1; 149 + syscall_raw(SYS_exit, 0, 0, 0, 0, 0, 0); 150 + return NULL; 151 + } 152 + 153 + /* 154 + * Verify that the sigsegv handler is invoked when pkey 0 is disabled. 155 + * Note that the new thread stack and the alternate signal stack is 156 + * protected by MPK 0. 157 + */ 158 + static void test_sigsegv_handler_with_pkey0_disabled(void) 159 + { 160 + struct sigaction sa; 161 + pthread_attr_t attr; 162 + pthread_t thr; 163 + 164 + sa.sa_flags = SA_SIGINFO; 165 + 166 + sa.sa_sigaction = sigsegv_handler; 167 + sigemptyset(&sa.sa_mask); 168 + if (sigaction(SIGSEGV, &sa, NULL) == -1) { 169 + perror("sigaction"); 170 + exit(EXIT_FAILURE); 171 + } 172 + 173 + memset(&siginfo, 0, sizeof(siginfo)); 174 + 175 + pthread_attr_init(&attr); 176 + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 177 + 178 + pthread_create(&thr, &attr, thread_segv_with_pkey0_disabled, NULL); 179 + 180 + pthread_mutex_lock(&mutex); 181 + while (siginfo.si_signo == 0) 182 + pthread_cond_wait(&cond, &mutex); 183 + pthread_mutex_unlock(&mutex); 184 + 185 + ksft_test_result(siginfo.si_signo == SIGSEGV && 186 + siginfo.si_code == SEGV_MAPERR && 187 + siginfo.si_addr == (void *)1, 188 + "%s\n", __func__); 189 + } 190 + 191 + /* 192 + * Verify that the sigsegv handler is invoked when pkey 0 is disabled. 193 + * Note that the new thread stack and the alternate signal stack is 194 + * protected by MPK 0, which renders them inaccessible when MPK 0 195 + * is disabled. So just the return from the thread should cause a 196 + * segfault with SEGV_PKUERR. 197 + */ 198 + static void test_sigsegv_handler_cannot_access_stack(void) 199 + { 200 + struct sigaction sa; 201 + pthread_attr_t attr; 202 + pthread_t thr; 203 + 204 + sa.sa_flags = SA_SIGINFO; 205 + 206 + sa.sa_sigaction = sigsegv_handler; 207 + sigemptyset(&sa.sa_mask); 208 + if (sigaction(SIGSEGV, &sa, NULL) == -1) { 209 + perror("sigaction"); 210 + exit(EXIT_FAILURE); 211 + } 212 + 213 + memset(&siginfo, 0, sizeof(siginfo)); 214 + 215 + pthread_attr_init(&attr); 216 + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 217 + 218 + pthread_create(&thr, &attr, thread_segv_pkuerr_stack, NULL); 219 + 220 + pthread_mutex_lock(&mutex); 221 + while (siginfo.si_signo == 0) 222 + pthread_cond_wait(&cond, &mutex); 223 + pthread_mutex_unlock(&mutex); 224 + 225 + ksft_test_result(siginfo.si_signo == SIGSEGV && 226 + siginfo.si_code == SEGV_PKUERR, 227 + "%s\n", __func__); 228 + } 229 + 230 + /* 231 + * Verify that the sigsegv handler that uses an alternate signal stack 232 + * is correctly invoked for a thread which uses a non-zero MPK to protect 233 + * its own stack, and disables all other MPKs (including 0). 234 + */ 235 + static void test_sigsegv_handler_with_different_pkey_for_stack(void) 236 + { 237 + struct sigaction sa; 238 + static stack_t sigstack; 239 + void *stack; 240 + int pkey; 241 + int parent_pid = 0; 242 + int child_pid = 0; 243 + 244 + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; 245 + 246 + sa.sa_sigaction = sigsegv_handler; 247 + 248 + sigemptyset(&sa.sa_mask); 249 + if (sigaction(SIGSEGV, &sa, NULL) == -1) { 250 + perror("sigaction"); 251 + exit(EXIT_FAILURE); 252 + } 253 + 254 + stack = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE, 255 + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 256 + 257 + assert(stack != MAP_FAILED); 258 + 259 + /* Allow access to MPK 0 and MPK 1 */ 260 + __write_pkey_reg(0x55555550); 261 + 262 + /* Protect the new stack with MPK 1 */ 263 + pkey = pkey_alloc(0, 0); 264 + pkey_mprotect(stack, STACK_SIZE, PROT_READ | PROT_WRITE, pkey); 265 + 266 + /* Set up alternate signal stack that will use the default MPK */ 267 + sigstack.ss_sp = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, 268 + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 269 + sigstack.ss_flags = 0; 270 + sigstack.ss_size = STACK_SIZE; 271 + 272 + memset(&siginfo, 0, sizeof(siginfo)); 273 + 274 + /* Use clone to avoid newer glibcs using rseq on new threads */ 275 + long ret = syscall_raw(SYS_clone, 276 + CLONE_VM | CLONE_FS | CLONE_FILES | 277 + CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | 278 + CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | 279 + CLONE_DETACHED, 280 + (long) ((char *)(stack) + STACK_SIZE), 281 + (long) &parent_pid, 282 + (long) &child_pid, 0, 0); 283 + 284 + if (ret < 0) { 285 + errno = -ret; 286 + perror("clone"); 287 + } else if (ret == 0) { 288 + thread_segv_maperr_ptr(&sigstack); 289 + syscall_raw(SYS_exit, 0, 0, 0, 0, 0, 0); 290 + } 291 + 292 + pthread_mutex_lock(&mutex); 293 + while (siginfo.si_signo == 0) 294 + pthread_cond_wait(&cond, &mutex); 295 + pthread_mutex_unlock(&mutex); 296 + 297 + ksft_test_result(siginfo.si_signo == SIGSEGV && 298 + siginfo.si_code == SEGV_MAPERR && 299 + siginfo.si_addr == (void *)1, 300 + "%s\n", __func__); 301 + } 302 + 303 + /* 304 + * Verify that the PKRU value set by the application is correctly 305 + * restored upon return from signal handling. 306 + */ 307 + static void test_pkru_preserved_after_sigusr1(void) 308 + { 309 + struct sigaction sa; 310 + unsigned long pkru = 0x45454544; 311 + 312 + sa.sa_flags = SA_SIGINFO; 313 + 314 + sa.sa_sigaction = sigusr1_handler; 315 + sigemptyset(&sa.sa_mask); 316 + if (sigaction(SIGUSR1, &sa, NULL) == -1) { 317 + perror("sigaction"); 318 + exit(EXIT_FAILURE); 319 + } 320 + 321 + memset(&siginfo, 0, sizeof(siginfo)); 322 + 323 + __write_pkey_reg(pkru); 324 + 325 + raise(SIGUSR1); 326 + 327 + pthread_mutex_lock(&mutex); 328 + while (siginfo.si_signo == 0) 329 + pthread_cond_wait(&cond, &mutex); 330 + pthread_mutex_unlock(&mutex); 331 + 332 + /* Ensure the pkru value is the same after returning from signal. */ 333 + ksft_test_result(pkru == __read_pkey_reg() && 334 + siginfo.si_signo == SIGUSR1, 335 + "%s\n", __func__); 336 + } 337 + 338 + static noinline void *thread_sigusr2_self(void *ptr) 339 + { 340 + /* 341 + * A const char array like "Resuming after SIGUSR2" won't be stored on 342 + * the stack and the code could access it via an offset from the program 343 + * counter. This makes sure it's on the function's stack frame. 344 + */ 345 + char str[] = {'R', 'e', 's', 'u', 'm', 'i', 'n', 'g', ' ', 346 + 'a', 'f', 't', 'e', 'r', ' ', 347 + 'S', 'I', 'G', 'U', 'S', 'R', '2', 348 + '.', '.', '.', '\n', '\0'}; 349 + stack_t *stack = ptr; 350 + 351 + /* 352 + * Setup alternate signal stack, which should be pkey_mprotect()ed by 353 + * MPK 0. The thread's stack cannot be used for signals because it is 354 + * not accessible by the default init_pkru value of 0x55555554. 355 + */ 356 + syscall(SYS_sigaltstack, (long)stack, 0, 0, 0, 0, 0); 357 + 358 + /* Disable MPK 0. Only MPK 2 is enabled. */ 359 + __write_pkey_reg(0x55555545); 360 + 361 + raise_sigusr2(); 362 + 363 + /* Do something, to show the thread resumed execution after the signal */ 364 + syscall_raw(SYS_write, 1, (long) str, sizeof(str) - 1, 0, 0, 0); 365 + 366 + /* 367 + * We can't return to test_pkru_sigreturn because it 368 + * will attempt to use a %rbp value which is on the stack 369 + * of the main thread. 370 + */ 371 + syscall_raw(SYS_exit, 0, 0, 0, 0, 0, 0); 372 + return NULL; 373 + } 374 + 375 + /* 376 + * Verify that sigreturn is able to restore altstack even if the thread had 377 + * disabled pkey 0. 378 + */ 379 + static void test_pkru_sigreturn(void) 380 + { 381 + struct sigaction sa = {0}; 382 + static stack_t sigstack; 383 + void *stack; 384 + int pkey; 385 + int parent_pid = 0; 386 + int child_pid = 0; 387 + 388 + sa.sa_handler = SIG_DFL; 389 + sa.sa_flags = 0; 390 + sigemptyset(&sa.sa_mask); 391 + 392 + /* 393 + * For this testcase, we do not want to handle SIGSEGV. Reset handler 394 + * to default so that the application can crash if it receives SIGSEGV. 395 + */ 396 + if (sigaction(SIGSEGV, &sa, NULL) == -1) { 397 + perror("sigaction"); 398 + exit(EXIT_FAILURE); 399 + } 400 + 401 + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; 402 + sa.sa_sigaction = sigusr2_handler; 403 + sigemptyset(&sa.sa_mask); 404 + 405 + if (sigaction(SIGUSR2, &sa, NULL) == -1) { 406 + perror("sigaction"); 407 + exit(EXIT_FAILURE); 408 + } 409 + 410 + stack = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE, 411 + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 412 + 413 + assert(stack != MAP_FAILED); 414 + 415 + /* 416 + * Allow access to MPK 0 and MPK 2. The child thread (to be created 417 + * later in this flow) will have its stack protected by MPK 2, whereas 418 + * the current thread's stack is protected by the default MPK 0. Hence 419 + * both need to be enabled. 420 + */ 421 + __write_pkey_reg(0x55555544); 422 + 423 + /* Protect the stack with MPK 2 */ 424 + pkey = pkey_alloc(0, 0); 425 + pkey_mprotect(stack, STACK_SIZE, PROT_READ | PROT_WRITE, pkey); 426 + 427 + /* Set up alternate signal stack that will use the default MPK */ 428 + sigstack.ss_sp = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, 429 + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 430 + sigstack.ss_flags = 0; 431 + sigstack.ss_size = STACK_SIZE; 432 + 433 + /* Use clone to avoid newer glibcs using rseq on new threads */ 434 + long ret = syscall_raw(SYS_clone, 435 + CLONE_VM | CLONE_FS | CLONE_FILES | 436 + CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | 437 + CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | 438 + CLONE_DETACHED, 439 + (long) ((char *)(stack) + STACK_SIZE), 440 + (long) &parent_pid, 441 + (long) &child_pid, 0, 0); 442 + 443 + if (ret < 0) { 444 + errno = -ret; 445 + perror("clone"); 446 + } else if (ret == 0) { 447 + thread_sigusr2_self(&sigstack); 448 + syscall_raw(SYS_exit, 0, 0, 0, 0, 0, 0); 449 + } 450 + 451 + child_pid = ret; 452 + /* Check that thread exited */ 453 + do { 454 + sched_yield(); 455 + ret = syscall_raw(SYS_tkill, child_pid, 0, 0, 0, 0, 0); 456 + } while (ret != -ESRCH && ret != -EINVAL); 457 + 458 + ksft_test_result_pass("%s\n", __func__); 459 + } 460 + 461 + static void (*pkey_tests[])(void) = { 462 + test_sigsegv_handler_with_pkey0_disabled, 463 + test_sigsegv_handler_cannot_access_stack, 464 + test_sigsegv_handler_with_different_pkey_for_stack, 465 + test_pkru_preserved_after_sigusr1, 466 + test_pkru_sigreturn 467 + }; 468 + 469 + int main(int argc, char *argv[]) 470 + { 471 + int i; 472 + 473 + ksft_print_header(); 474 + ksft_set_plan(ARRAY_SIZE(pkey_tests)); 475 + 476 + for (i = 0; i < ARRAY_SIZE(pkey_tests); i++) 477 + (*pkey_tests[i])(); 478 + 479 + ksft_finished(); 480 + return 0; 481 + }
-10
tools/testing/selftests/mm/protection_keys.c
··· 950 950 nr_test_fds = 0; 951 951 } 952 952 953 - #define barrier() __asm__ __volatile__("": : :"memory") 954 - __attribute__((noinline)) int read_ptr(int *ptr) 955 - { 956 - /* 957 - * Keep GCC from optimizing this away somehow 958 - */ 959 - barrier(); 960 - return *ptr; 961 - } 962 - 963 953 void test_pkey_alloc_free_attach_pkey0(int *ptr, u16 pkey) 964 954 { 965 955 int i, err;