| // SPDX-License-Identifier: GPL-2.0+ |
| // |
| // Copyright 2022, Michael Ellerman, IBM Corp. |
| // |
| // Test that the 4PB address space SLB handling doesn't corrupt userspace registers |
| // (r9-r13) due to a SLB fault while saving the PPR. |
| // |
| // The bug was introduced in f384796c4 ("powerpc/mm: Add support for handling > 512TB |
| // address in SLB miss") and fixed in 4c2de74cc869 ("powerpc/64: Interrupts save PPR on |
| // stack rather than thread_struct"). |
| // |
| // To hit the bug requires the task struct and kernel stack to be in different segments. |
| // Usually that requires more than 1TB of RAM, or if that's not practical, boot the kernel |
| // with "disable_1tb_segments". |
| // |
| // The test works by creating mappings above 512TB, to trigger the large address space |
| // support. It creates 64 mappings, double the size of the SLB, to cause SLB faults on |
| // each access (assuming naive replacement). It then loops over those mappings touching |
| // each, and checks that r9-r13 aren't corrupted. |
| // |
| // It then forks another child and tries again, because a new child process will get a new |
| // kernel stack and thread struct allocated, which may be more optimally placed to trigger |
| // the bug. It would probably be better to leave the previous child processes hanging |
| // around, so that kernel stack & thread struct allocations are not reused, but that would |
| // amount to a 30 second fork bomb. The current design reliably triggers the bug on |
| // unpatched kernels. |
| |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/mman.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include "utils.h" |
| |
| #ifndef MAP_FIXED_NOREPLACE |
| #define MAP_FIXED_NOREPLACE MAP_FIXED // "Should be safe" above 512TB |
| #endif |
| |
| #define BASE_ADDRESS (1ul << 50) // 1PB |
| #define STRIDE (2ul << 40) // 2TB |
| #define SLB_SIZE 32 |
| #define NR_MAPPINGS (SLB_SIZE * 2) |
| |
| static volatile sig_atomic_t signaled; |
| |
| static void signal_handler(int sig) |
| { |
| signaled = 1; |
| } |
| |
| #define CHECK_REG(_reg) \ |
| if (_reg != _reg##_orig) { \ |
| printf(str(_reg) " corrupted! Expected 0x%lx != 0x%lx\n", _reg##_orig, \ |
| _reg); \ |
| _exit(1); \ |
| } |
| |
| static int touch_mappings(void) |
| { |
| unsigned long r9_orig, r10_orig, r11_orig, r12_orig, r13_orig; |
| unsigned long r9, r10, r11, r12, r13; |
| unsigned long addr, *p; |
| int i; |
| |
| for (i = 0; i < NR_MAPPINGS; i++) { |
| addr = BASE_ADDRESS + (i * STRIDE); |
| p = (unsigned long *)addr; |
| |
| asm volatile("mr %0, %%r9 ;" // Read original GPR values |
| "mr %1, %%r10 ;" |
| "mr %2, %%r11 ;" |
| "mr %3, %%r12 ;" |
| "mr %4, %%r13 ;" |
| "std %10, 0(%11) ;" // Trigger SLB fault |
| "mr %5, %%r9 ;" // Save possibly corrupted values |
| "mr %6, %%r10 ;" |
| "mr %7, %%r11 ;" |
| "mr %8, %%r12 ;" |
| "mr %9, %%r13 ;" |
| "mr %%r9, %0 ;" // Restore original values |
| "mr %%r10, %1 ;" |
| "mr %%r11, %2 ;" |
| "mr %%r12, %3 ;" |
| "mr %%r13, %4 ;" |
| : "=&b"(r9_orig), "=&b"(r10_orig), "=&b"(r11_orig), |
| "=&b"(r12_orig), "=&b"(r13_orig), "=&b"(r9), "=&b"(r10), |
| "=&b"(r11), "=&b"(r12), "=&b"(r13) |
| : "b"(i), "b"(p) |
| : "r9", "r10", "r11", "r12", "r13"); |
| |
| CHECK_REG(r9); |
| CHECK_REG(r10); |
| CHECK_REG(r11); |
| CHECK_REG(r12); |
| CHECK_REG(r13); |
| } |
| |
| return 0; |
| } |
| |
| static int test(void) |
| { |
| unsigned long page_size, addr, *p; |
| struct sigaction action; |
| bool hash_mmu; |
| int i, status; |
| pid_t pid; |
| |
| // This tests a hash MMU specific bug. |
| FAIL_IF(using_hash_mmu(&hash_mmu)); |
| SKIP_IF(!hash_mmu); |
| // 4K kernels don't support 4PB address space |
| SKIP_IF(sysconf(_SC_PAGESIZE) < 65536); |
| |
| page_size = sysconf(_SC_PAGESIZE); |
| |
| for (i = 0; i < NR_MAPPINGS; i++) { |
| addr = BASE_ADDRESS + (i * STRIDE); |
| |
| p = mmap((void *)addr, page_size, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); |
| if (p == MAP_FAILED) { |
| perror("mmap"); |
| printf("Error: couldn't mmap(), confirm kernel has 4PB support?\n"); |
| return 1; |
| } |
| } |
| |
| action.sa_handler = signal_handler; |
| action.sa_flags = SA_RESTART; |
| FAIL_IF(sigaction(SIGALRM, &action, NULL) < 0); |
| |
| // Seen to always crash in under ~10s on affected kernels. |
| alarm(30); |
| |
| while (!signaled) { |
| // Fork new processes, to increase the chance that we hit the case where |
| // the kernel stack and task struct are in different segments. |
| pid = fork(); |
| if (pid == 0) |
| exit(touch_mappings()); |
| |
| FAIL_IF(waitpid(-1, &status, 0) == -1); |
| FAIL_IF(WIFSIGNALED(status)); |
| FAIL_IF(!WIFEXITED(status)); |
| FAIL_IF(WEXITSTATUS(status)); |
| } |
| |
| return 0; |
| } |
| |
| int main(void) |
| { |
| return test_harness(test, "large_vm_gpr_corruption"); |
| } |