riscv: selftests: Add a pointer masking test
authorSamuel Holland <samuel.holland@sifive.com>
Wed, 16 Oct 2024 20:27:48 +0000 (13:27 -0700)
committerPalmer Dabbelt <palmer@rivosinc.com>
Thu, 24 Oct 2024 21:12:58 +0000 (14:12 -0700)
This test covers the behavior of the PR_SET_TAGGED_ADDR_CTRL and
PR_GET_TAGGED_ADDR_CTRL prctl() operations, their effects on the
userspace ABI, and their effects on the system call ABI.

Reviewed-by: Charlie Jenkins <charlie@rivosinc.com>
Tested-by: Charlie Jenkins <charlie@rivosinc.com>
Signed-off-by: Samuel Holland <samuel.holland@sifive.com>
Link: https://lore.kernel.org/r/20241016202814.4061541-8-samuel.holland@sifive.com
Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
tools/testing/selftests/riscv/Makefile
tools/testing/selftests/riscv/abi/.gitignore [new file with mode: 0644]
tools/testing/selftests/riscv/abi/Makefile [new file with mode: 0644]
tools/testing/selftests/riscv/abi/pointer_masking.c [new file with mode: 0644]

index 7ce03d832b6472502ee2fc26c97b0161355aa8cb..099b8c1f46f89f7c5074701fbc723bb80399652b 100644 (file)
@@ -5,7 +5,7 @@
 ARCH ?= $(shell uname -m 2>/dev/null || echo not)
 
 ifneq (,$(filter $(ARCH),riscv))
-RISCV_SUBTARGETS ?= hwprobe vector mm sigreturn
+RISCV_SUBTARGETS ?= abi hwprobe mm sigreturn vector
 else
 RISCV_SUBTARGETS :=
 endif
diff --git a/tools/testing/selftests/riscv/abi/.gitignore b/tools/testing/selftests/riscv/abi/.gitignore
new file mode 100644 (file)
index 0000000..b38358f
--- /dev/null
@@ -0,0 +1 @@
+pointer_masking
diff --git a/tools/testing/selftests/riscv/abi/Makefile b/tools/testing/selftests/riscv/abi/Makefile
new file mode 100644 (file)
index 0000000..ed82ff9
--- /dev/null
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+
+CFLAGS += -I$(top_srcdir)/tools/include
+
+TEST_GEN_PROGS := pointer_masking
+
+include ../../lib.mk
+
+$(OUTPUT)/pointer_masking: pointer_masking.c
+       $(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^
diff --git a/tools/testing/selftests/riscv/abi/pointer_masking.c b/tools/testing/selftests/riscv/abi/pointer_masking.c
new file mode 100644 (file)
index 0000000..dee41b7
--- /dev/null
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <errno.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "../../kselftest.h"
+
+#ifndef PR_PMLEN_SHIFT
+#define PR_PMLEN_SHIFT                 24
+#endif
+#ifndef PR_PMLEN_MASK
+#define PR_PMLEN_MASK                  (0x7fUL << PR_PMLEN_SHIFT)
+#endif
+
+static int dev_zero;
+
+static int pipefd[2];
+
+static sigjmp_buf jmpbuf;
+
+static void sigsegv_handler(int sig)
+{
+       siglongjmp(jmpbuf, 1);
+}
+
+static int min_pmlen;
+static int max_pmlen;
+
+static inline bool valid_pmlen(int pmlen)
+{
+       return pmlen == 0 || pmlen == 7 || pmlen == 16;
+}
+
+static void test_pmlen(void)
+{
+       ksft_print_msg("Testing available PMLEN values\n");
+
+       for (int request = 0; request <= 16; request++) {
+               int pmlen, ret;
+
+               ret = prctl(PR_SET_TAGGED_ADDR_CTRL, request << PR_PMLEN_SHIFT, 0, 0, 0);
+               if (ret)
+                       goto pr_set_error;
+
+               ret = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+               ksft_test_result(ret >= 0, "PMLEN=%d PR_GET_TAGGED_ADDR_CTRL\n", request);
+               if (ret < 0)
+                       goto pr_get_error;
+
+               pmlen = (ret & PR_PMLEN_MASK) >> PR_PMLEN_SHIFT;
+               ksft_test_result(pmlen >= request, "PMLEN=%d constraint\n", request);
+               ksft_test_result(valid_pmlen(pmlen), "PMLEN=%d validity\n", request);
+
+               if (min_pmlen == 0)
+                       min_pmlen = pmlen;
+               if (max_pmlen < pmlen)
+                       max_pmlen = pmlen;
+
+               continue;
+
+pr_set_error:
+               ksft_test_result_skip("PMLEN=%d PR_GET_TAGGED_ADDR_CTRL\n", request);
+pr_get_error:
+               ksft_test_result_skip("PMLEN=%d constraint\n", request);
+               ksft_test_result_skip("PMLEN=%d validity\n", request);
+       }
+
+       if (max_pmlen == 0)
+               ksft_exit_fail_msg("Failed to enable pointer masking\n");
+}
+
+static int set_tagged_addr_ctrl(int pmlen, bool tagged_addr_abi)
+{
+       int arg, ret;
+
+       arg = pmlen << PR_PMLEN_SHIFT | tagged_addr_abi;
+       ret = prctl(PR_SET_TAGGED_ADDR_CTRL, arg, 0, 0, 0);
+       if (!ret) {
+               ret = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+               if (ret == arg)
+                       return 0;
+       }
+
+       return ret < 0 ? -errno : -ENODATA;
+}
+
+static void test_dereference_pmlen(int pmlen)
+{
+       static volatile int i;
+       volatile int *p;
+       int ret;
+
+       ret = set_tagged_addr_ctrl(pmlen, false);
+       if (ret)
+               return ksft_test_result_error("PMLEN=%d setup (%d)\n", pmlen, ret);
+
+       i = pmlen;
+
+       if (pmlen) {
+               p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen));
+
+               /* These dereferences should succeed. */
+               if (sigsetjmp(jmpbuf, 1))
+                       return ksft_test_result_fail("PMLEN=%d valid tag\n", pmlen);
+               if (*p != pmlen)
+                       return ksft_test_result_fail("PMLEN=%d bad value\n", pmlen);
+               ++*p;
+       }
+
+       p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen - 1));
+
+       /* These dereferences should raise SIGSEGV. */
+       if (sigsetjmp(jmpbuf, 1))
+               return ksft_test_result_pass("PMLEN=%d dereference\n", pmlen);
+       ++*p;
+       ksft_test_result_fail("PMLEN=%d invalid tag\n", pmlen);
+}
+
+static void test_dereference(void)
+{
+       ksft_print_msg("Testing userspace pointer dereference\n");
+
+       signal(SIGSEGV, sigsegv_handler);
+
+       test_dereference_pmlen(0);
+       test_dereference_pmlen(min_pmlen);
+       test_dereference_pmlen(max_pmlen);
+
+       signal(SIGSEGV, SIG_DFL);
+}
+
+static void execve_child_sigsegv_handler(int sig)
+{
+       exit(42);
+}
+
+static int execve_child(void)
+{
+       static volatile int i;
+       volatile int *p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - 7));
+
+       signal(SIGSEGV, execve_child_sigsegv_handler);
+
+       /* This dereference should raise SIGSEGV. */
+       return *p;
+}
+
+static void test_fork_exec(void)
+{
+       int ret, status;
+
+       ksft_print_msg("Testing fork/exec behavior\n");
+
+       ret = set_tagged_addr_ctrl(min_pmlen, false);
+       if (ret)
+               return ksft_test_result_error("setup (%d)\n", ret);
+
+       if (fork()) {
+               wait(&status);
+               ksft_test_result(WIFEXITED(status) && WEXITSTATUS(status) == 42,
+                                "dereference after fork\n");
+       } else {
+               static volatile int i = 42;
+               volatile int *p;
+
+               p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - min_pmlen));
+
+               /* This dereference should succeed. */
+               exit(*p);
+       }
+
+       if (fork()) {
+               wait(&status);
+               ksft_test_result(WIFEXITED(status) && WEXITSTATUS(status) == 42,
+                                "dereference after fork+exec\n");
+       } else {
+               /* Will call execve_child(). */
+               execve("/proc/self/exe", (char *const []) { "", NULL }, NULL);
+       }
+}
+
+static void test_tagged_addr_abi_sysctl(void)
+{
+       char value;
+       int fd;
+
+       ksft_print_msg("Testing tagged address ABI sysctl\n");
+
+       fd = open("/proc/sys/abi/tagged_addr_disabled", O_WRONLY);
+       if (fd < 0) {
+               ksft_test_result_skip("failed to open sysctl file\n");
+               ksft_test_result_skip("failed to open sysctl file\n");
+               return;
+       }
+
+       value = '1';
+       pwrite(fd, &value, 1, 0);
+       ksft_test_result(set_tagged_addr_ctrl(min_pmlen, true) == -EINVAL,
+                        "sysctl disabled\n");
+
+       value = '0';
+       pwrite(fd, &value, 1, 0);
+       ksft_test_result(set_tagged_addr_ctrl(min_pmlen, true) == 0,
+                        "sysctl enabled\n");
+
+       set_tagged_addr_ctrl(0, false);
+
+       close(fd);
+}
+
+static void test_tagged_addr_abi_pmlen(int pmlen)
+{
+       int i, *p, ret;
+
+       i = ~pmlen;
+
+       if (pmlen) {
+               p = (int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen));
+
+               ret = set_tagged_addr_ctrl(pmlen, false);
+               if (ret)
+                       return ksft_test_result_error("PMLEN=%d ABI disabled setup (%d)\n",
+                                                     pmlen, ret);
+
+               ret = write(pipefd[1], p, sizeof(*p));
+               if (ret >= 0 || errno != EFAULT)
+                       return ksft_test_result_fail("PMLEN=%d ABI disabled write\n", pmlen);
+
+               ret = read(dev_zero, p, sizeof(*p));
+               if (ret >= 0 || errno != EFAULT)
+                       return ksft_test_result_fail("PMLEN=%d ABI disabled read\n", pmlen);
+
+               if (i != ~pmlen)
+                       return ksft_test_result_fail("PMLEN=%d ABI disabled value\n", pmlen);
+
+               ret = set_tagged_addr_ctrl(pmlen, true);
+               if (ret)
+                       return ksft_test_result_error("PMLEN=%d ABI enabled setup (%d)\n",
+                                                     pmlen, ret);
+
+               ret = write(pipefd[1], p, sizeof(*p));
+               if (ret != sizeof(*p))
+                       return ksft_test_result_fail("PMLEN=%d ABI enabled write\n", pmlen);
+
+               ret = read(dev_zero, p, sizeof(*p));
+               if (ret != sizeof(*p))
+                       return ksft_test_result_fail("PMLEN=%d ABI enabled read\n", pmlen);
+
+               if (i)
+                       return ksft_test_result_fail("PMLEN=%d ABI enabled value\n", pmlen);
+
+               i = ~pmlen;
+       } else {
+               /* The tagged address ABI cannot be enabled when PMLEN == 0. */
+               ret = set_tagged_addr_ctrl(pmlen, true);
+               if (ret != -EINVAL)
+                       return ksft_test_result_error("PMLEN=%d ABI setup (%d)\n",
+                                                     pmlen, ret);
+       }
+
+       p = (int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen - 1));
+
+       ret = write(pipefd[1], p, sizeof(*p));
+       if (ret >= 0 || errno != EFAULT)
+               return ksft_test_result_fail("PMLEN=%d invalid tag write (%d)\n", pmlen, errno);
+
+       ret = read(dev_zero, p, sizeof(*p));
+       if (ret >= 0 || errno != EFAULT)
+               return ksft_test_result_fail("PMLEN=%d invalid tag read\n", pmlen);
+
+       if (i != ~pmlen)
+               return ksft_test_result_fail("PMLEN=%d invalid tag value\n", pmlen);
+
+       ksft_test_result_pass("PMLEN=%d tagged address ABI\n", pmlen);
+}
+
+static void test_tagged_addr_abi(void)
+{
+       ksft_print_msg("Testing tagged address ABI\n");
+
+       test_tagged_addr_abi_pmlen(0);
+       test_tagged_addr_abi_pmlen(min_pmlen);
+       test_tagged_addr_abi_pmlen(max_pmlen);
+}
+
+static struct test_info {
+       unsigned int nr_tests;
+       void (*test_fn)(void);
+} tests[] = {
+       { .nr_tests = 17 * 3, test_pmlen },
+       { .nr_tests = 3, test_dereference },
+       { .nr_tests = 2, test_fork_exec },
+       { .nr_tests = 2, test_tagged_addr_abi_sysctl },
+       { .nr_tests = 3, test_tagged_addr_abi },
+};
+
+int main(int argc, char **argv)
+{
+       unsigned int plan = 0;
+       int ret;
+
+       /* Check if this is the child process after execve(). */
+       if (!argv[0][0])
+               return execve_child();
+
+       dev_zero = open("/dev/zero", O_RDWR);
+       if (dev_zero < 0)
+               return 1;
+
+       /* Write to a pipe so the kernel must dereference the buffer pointer. */
+       ret = pipe(pipefd);
+       if (ret)
+               return 1;
+
+       ksft_print_header();
+
+       for (int i = 0; i < ARRAY_SIZE(tests); i++)
+               plan += tests[i].nr_tests;
+
+       ksft_set_plan(plan);
+
+       for (int i = 0; i < ARRAY_SIZE(tests); i++)
+               tests[i].test_fn();
+
+       ksft_finished();
+}