selftests/powerpc: Add a test of bad (out-of-range) accesses
authorMichael Ellerman <mpe@ellerman.id.au>
Mon, 20 May 2019 10:20:51 +0000 (20:20 +1000)
committerMichael Ellerman <mpe@ellerman.id.au>
Mon, 6 Jan 2020 05:25:27 +0000 (16:25 +1100)
Userspace isn't allowed to access certain address ranges, make sure we
actually test that to at least some degree.

This would have caught the recent bug where the SLB fault handler was
incorrectly called on an out-of-range access when using the Radix MMU.
It also would have caught the bug we had in get_region_id() where we
were inserting SLB entries for bad addresses.

Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20190520102051.12103-1-mpe@ellerman.id.au
tools/testing/selftests/powerpc/mm/.gitignore
tools/testing/selftests/powerpc/mm/Makefile
tools/testing/selftests/powerpc/mm/bad_accesses.c [new file with mode: 0644]

index 7101ffd08d66e29cd0ab3068d0afb8d9633ca7ef..0ebeaea22641298b4b46d77b06bc915cbb5ed5a3 100644 (file)
@@ -5,3 +5,4 @@ prot_sao
 segv_errors
 wild_bctr
 large_vm_fork_separation
+bad_accesses
index ed1565809d2b0bd3f788031f855e9ceba209e2c9..b9103c4bb4145f2b61f10c95409a03e9df18e6e0 100644 (file)
@@ -3,7 +3,7 @@ noarg:
        $(MAKE) -C ../
 
 TEST_GEN_PROGS := hugetlb_vs_thp_test subpage_prot prot_sao segv_errors wild_bctr \
-                 large_vm_fork_separation
+                 large_vm_fork_separation bad_accesses
 TEST_GEN_PROGS_EXTENDED := tlbie_test
 TEST_GEN_FILES := tempfile
 
@@ -16,6 +16,7 @@ $(OUTPUT)/prot_sao: ../utils.c
 
 $(OUTPUT)/wild_bctr: CFLAGS += -m64
 $(OUTPUT)/large_vm_fork_separation: CFLAGS += -m64
+$(OUTPUT)/bad_accesses: CFLAGS += -m64
 
 $(OUTPUT)/tempfile:
        dd if=/dev/zero of=$@ bs=64k count=1
diff --git a/tools/testing/selftests/powerpc/mm/bad_accesses.c b/tools/testing/selftests/powerpc/mm/bad_accesses.c
new file mode 100644 (file)
index 0000000..adc465f
--- /dev/null
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Copyright 2019, Michael Ellerman, IBM Corp.
+//
+// Test that out-of-bounds reads/writes behave as expected.
+
+#include <setjmp.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "utils.h"
+
+// Old distros (Ubuntu 16.04 at least) don't define this
+#ifndef SEGV_BNDERR
+#define SEGV_BNDERR    3
+#endif
+
+// 64-bit kernel is always here
+#define PAGE_OFFSET    (0xcul << 60)
+
+static unsigned long kernel_virt_end;
+
+static volatile int fault_code;
+static volatile unsigned long fault_addr;
+static jmp_buf setjmp_env;
+
+static void segv_handler(int n, siginfo_t *info, void *ctxt_v)
+{
+       fault_code = info->si_code;
+       fault_addr = (unsigned long)info->si_addr;
+       siglongjmp(setjmp_env, 1);
+}
+
+int bad_access(char *p, bool write)
+{
+       char x;
+
+       fault_code = 0;
+       fault_addr = 0;
+
+       if (sigsetjmp(setjmp_env, 1) == 0) {
+               if (write)
+                       *p = 1;
+               else
+                       x = *p;
+
+               printf("Bad - no SEGV! (%c)\n", x);
+               return 1;
+       }
+
+       // If we see MAPERR that means we took a page fault rather than an SLB
+       // miss. We only expect to take page faults for addresses within the
+       // valid kernel range.
+       FAIL_IF(fault_code == SEGV_MAPERR && \
+               (fault_addr < PAGE_OFFSET || fault_addr >= kernel_virt_end));
+
+       FAIL_IF(fault_code != SEGV_MAPERR && fault_code != SEGV_BNDERR);
+
+       return 0;
+}
+
+static int using_hash_mmu(bool *using_hash)
+{
+       char line[128];
+       FILE *f;
+       int rc;
+
+       f = fopen("/proc/cpuinfo", "r");
+       FAIL_IF(!f);
+
+       rc = 0;
+       while (fgets(line, sizeof(line), f) != NULL) {
+               if (strcmp(line, "MMU           : Hash\n") == 0) {
+                       *using_hash = true;
+                       goto out;
+               }
+
+               if (strcmp(line, "MMU           : Radix\n") == 0) {
+                       *using_hash = false;
+                       goto out;
+               }
+       }
+
+       rc = -1;
+out:
+       fclose(f);
+       return rc;
+}
+
+static int test(void)
+{
+       unsigned long i, j, addr, region_shift, page_shift, page_size;
+       struct sigaction sig;
+       bool hash_mmu;
+
+       sig = (struct sigaction) {
+               .sa_sigaction = segv_handler,
+               .sa_flags = SA_SIGINFO,
+       };
+
+       FAIL_IF(sigaction(SIGSEGV, &sig, NULL) != 0);
+
+       FAIL_IF(using_hash_mmu(&hash_mmu));
+
+       page_size = sysconf(_SC_PAGESIZE);
+       if (page_size == (64 * 1024))
+               page_shift = 16;
+       else
+               page_shift = 12;
+
+       if (page_size == (64 * 1024) || !hash_mmu) {
+               region_shift = 52;
+
+               // We have 7 512T regions (4 kernel linear, vmalloc, io, vmemmap)
+               kernel_virt_end = PAGE_OFFSET + (7 * (512ul << 40));
+       } else if (page_size == (4 * 1024) && hash_mmu) {
+               region_shift = 46;
+
+               // We have 7 64T regions (4 kernel linear, vmalloc, io, vmemmap)
+               kernel_virt_end = PAGE_OFFSET + (7 * (64ul << 40));
+       } else
+               FAIL_IF(true);
+
+       printf("Using %s MMU, PAGE_SIZE = %dKB start address 0x%016lx\n",
+              hash_mmu ? "hash" : "radix",
+              (1 << page_shift) >> 10,
+              1ul << region_shift);
+
+       // This generates access patterns like:
+       //   0x0010000000000000
+       //   0x0010000000010000
+       //   0x0010000000020000
+       //   ...
+       //   0x0014000000000000
+       //   0x0018000000000000
+       //   0x0020000000000000
+       //   0x0020000000010000
+       //   0x0020000000020000
+       //   ...
+       //   0xf400000000000000
+       //   0xf800000000000000
+
+       for (i = 1; i <= ((0xful << 60) >> region_shift); i++) {
+               for (j = page_shift - 1; j < 60; j++) {
+                       unsigned long base, delta;
+
+                       base  = i << region_shift;
+                       delta = 1ul << j;
+
+                       if (delta >= base)
+                               break;
+
+                       addr = (base | delta) & ~((1 << page_shift) - 1);
+
+                       FAIL_IF(bad_access((char *)addr, false));
+                       FAIL_IF(bad_access((char *)addr, true));
+               }
+       }
+
+       return 0;
+}
+
+int main(void)
+{
+       return test_harness(test, "bad_accesses");
+}