Blob Blame History Raw
From 5eb7cfb3a2b178f3d443301cda0825bb9f475657 Mon Sep 17 00:00:00 2001
From: Michael Ellerman <mpe@ellerman.id.au>
Date: Mon, 20 May 2019 20:20:51 +1000
Subject: [PATCH] selftests/powerpc: Add a test of bad (out-of-range) accesses

References: bsc#1181158 ltc#190851
Patch-mainline: v5.6-rc1
Git-commit: 5eb7cfb3a2b178f3d443301cda0825bb9f475657

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
Acked-by: Michal Suchanek <msuchanek@suse.de>
---
 tools/testing/selftests/powerpc/mm/.gitignore |   1 +
 tools/testing/selftests/powerpc/mm/Makefile   |   3 +-
 .../selftests/powerpc/mm/bad_accesses.c       | 171 ++++++++++++++++++
 3 files changed, 174 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/powerpc/mm/bad_accesses.c

diff --git a/tools/testing/selftests/powerpc/mm/.gitignore b/tools/testing/selftests/powerpc/mm/.gitignore
index 7101ffd08d66..0ebeaea22641 100644
--- a/tools/testing/selftests/powerpc/mm/.gitignore
+++ b/tools/testing/selftests/powerpc/mm/.gitignore
@@ -5,3 +5,4 @@ prot_sao
 segv_errors
 wild_bctr
 large_vm_fork_separation
+bad_accesses
diff --git a/tools/testing/selftests/powerpc/mm/Makefile b/tools/testing/selftests/powerpc/mm/Makefile
index ed1565809d2b..b9103c4bb414 100644
--- a/tools/testing/selftests/powerpc/mm/Makefile
+++ b/tools/testing/selftests/powerpc/mm/Makefile
@@ -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
index 000000000000..adc465f499ef
--- /dev/null
+++ b/tools/testing/selftests/powerpc/mm/bad_accesses.c
@@ -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");
+}
-- 
2.26.2