17470b5afSSamuel Holland // SPDX-License-Identifier: GPL-2.0-only
27470b5afSSamuel Holland
37470b5afSSamuel Holland #include <errno.h>
47470b5afSSamuel Holland #include <fcntl.h>
57470b5afSSamuel Holland #include <setjmp.h>
67470b5afSSamuel Holland #include <signal.h>
77470b5afSSamuel Holland #include <stdbool.h>
87470b5afSSamuel Holland #include <sys/prctl.h>
97470b5afSSamuel Holland #include <sys/wait.h>
107470b5afSSamuel Holland #include <unistd.h>
117470b5afSSamuel Holland
127470b5afSSamuel Holland #include "../../kselftest.h"
137470b5afSSamuel Holland
147470b5afSSamuel Holland #ifndef PR_PMLEN_SHIFT
157470b5afSSamuel Holland #define PR_PMLEN_SHIFT 24
167470b5afSSamuel Holland #endif
177470b5afSSamuel Holland #ifndef PR_PMLEN_MASK
187470b5afSSamuel Holland #define PR_PMLEN_MASK (0x7fUL << PR_PMLEN_SHIFT)
197470b5afSSamuel Holland #endif
207470b5afSSamuel Holland
217470b5afSSamuel Holland static int dev_zero;
227470b5afSSamuel Holland
237470b5afSSamuel Holland static int pipefd[2];
247470b5afSSamuel Holland
257470b5afSSamuel Holland static sigjmp_buf jmpbuf;
267470b5afSSamuel Holland
sigsegv_handler(int sig)277470b5afSSamuel Holland static void sigsegv_handler(int sig)
287470b5afSSamuel Holland {
297470b5afSSamuel Holland siglongjmp(jmpbuf, 1);
307470b5afSSamuel Holland }
317470b5afSSamuel Holland
327470b5afSSamuel Holland static int min_pmlen;
337470b5afSSamuel Holland static int max_pmlen;
347470b5afSSamuel Holland
valid_pmlen(int pmlen)357470b5afSSamuel Holland static inline bool valid_pmlen(int pmlen)
367470b5afSSamuel Holland {
377470b5afSSamuel Holland return pmlen == 0 || pmlen == 7 || pmlen == 16;
387470b5afSSamuel Holland }
397470b5afSSamuel Holland
test_pmlen(void)407470b5afSSamuel Holland static void test_pmlen(void)
417470b5afSSamuel Holland {
427470b5afSSamuel Holland ksft_print_msg("Testing available PMLEN values\n");
437470b5afSSamuel Holland
447470b5afSSamuel Holland for (int request = 0; request <= 16; request++) {
457470b5afSSamuel Holland int pmlen, ret;
467470b5afSSamuel Holland
477470b5afSSamuel Holland ret = prctl(PR_SET_TAGGED_ADDR_CTRL, request << PR_PMLEN_SHIFT, 0, 0, 0);
487470b5afSSamuel Holland if (ret)
497470b5afSSamuel Holland goto pr_set_error;
507470b5afSSamuel Holland
517470b5afSSamuel Holland ret = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
527470b5afSSamuel Holland ksft_test_result(ret >= 0, "PMLEN=%d PR_GET_TAGGED_ADDR_CTRL\n", request);
537470b5afSSamuel Holland if (ret < 0)
547470b5afSSamuel Holland goto pr_get_error;
557470b5afSSamuel Holland
567470b5afSSamuel Holland pmlen = (ret & PR_PMLEN_MASK) >> PR_PMLEN_SHIFT;
577470b5afSSamuel Holland ksft_test_result(pmlen >= request, "PMLEN=%d constraint\n", request);
587470b5afSSamuel Holland ksft_test_result(valid_pmlen(pmlen), "PMLEN=%d validity\n", request);
597470b5afSSamuel Holland
607470b5afSSamuel Holland if (min_pmlen == 0)
617470b5afSSamuel Holland min_pmlen = pmlen;
627470b5afSSamuel Holland if (max_pmlen < pmlen)
637470b5afSSamuel Holland max_pmlen = pmlen;
647470b5afSSamuel Holland
657470b5afSSamuel Holland continue;
667470b5afSSamuel Holland
677470b5afSSamuel Holland pr_set_error:
687470b5afSSamuel Holland ksft_test_result_skip("PMLEN=%d PR_GET_TAGGED_ADDR_CTRL\n", request);
697470b5afSSamuel Holland pr_get_error:
707470b5afSSamuel Holland ksft_test_result_skip("PMLEN=%d constraint\n", request);
717470b5afSSamuel Holland ksft_test_result_skip("PMLEN=%d validity\n", request);
727470b5afSSamuel Holland }
737470b5afSSamuel Holland
747470b5afSSamuel Holland if (max_pmlen == 0)
757470b5afSSamuel Holland ksft_exit_fail_msg("Failed to enable pointer masking\n");
767470b5afSSamuel Holland }
777470b5afSSamuel Holland
set_tagged_addr_ctrl(int pmlen,bool tagged_addr_abi)787470b5afSSamuel Holland static int set_tagged_addr_ctrl(int pmlen, bool tagged_addr_abi)
797470b5afSSamuel Holland {
807470b5afSSamuel Holland int arg, ret;
817470b5afSSamuel Holland
827470b5afSSamuel Holland arg = pmlen << PR_PMLEN_SHIFT | tagged_addr_abi;
837470b5afSSamuel Holland ret = prctl(PR_SET_TAGGED_ADDR_CTRL, arg, 0, 0, 0);
847470b5afSSamuel Holland if (!ret) {
857470b5afSSamuel Holland ret = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
867470b5afSSamuel Holland if (ret == arg)
877470b5afSSamuel Holland return 0;
887470b5afSSamuel Holland }
897470b5afSSamuel Holland
907470b5afSSamuel Holland return ret < 0 ? -errno : -ENODATA;
917470b5afSSamuel Holland }
927470b5afSSamuel Holland
test_dereference_pmlen(int pmlen)937470b5afSSamuel Holland static void test_dereference_pmlen(int pmlen)
947470b5afSSamuel Holland {
957470b5afSSamuel Holland static volatile int i;
967470b5afSSamuel Holland volatile int *p;
977470b5afSSamuel Holland int ret;
987470b5afSSamuel Holland
997470b5afSSamuel Holland ret = set_tagged_addr_ctrl(pmlen, false);
1007470b5afSSamuel Holland if (ret)
1017470b5afSSamuel Holland return ksft_test_result_error("PMLEN=%d setup (%d)\n", pmlen, ret);
1027470b5afSSamuel Holland
1037470b5afSSamuel Holland i = pmlen;
1047470b5afSSamuel Holland
1057470b5afSSamuel Holland if (pmlen) {
1067470b5afSSamuel Holland p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen));
1077470b5afSSamuel Holland
1087470b5afSSamuel Holland /* These dereferences should succeed. */
1097470b5afSSamuel Holland if (sigsetjmp(jmpbuf, 1))
1107470b5afSSamuel Holland return ksft_test_result_fail("PMLEN=%d valid tag\n", pmlen);
1117470b5afSSamuel Holland if (*p != pmlen)
1127470b5afSSamuel Holland return ksft_test_result_fail("PMLEN=%d bad value\n", pmlen);
1137470b5afSSamuel Holland ++*p;
1147470b5afSSamuel Holland }
1157470b5afSSamuel Holland
1167470b5afSSamuel Holland p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen - 1));
1177470b5afSSamuel Holland
1187470b5afSSamuel Holland /* These dereferences should raise SIGSEGV. */
1197470b5afSSamuel Holland if (sigsetjmp(jmpbuf, 1))
1207470b5afSSamuel Holland return ksft_test_result_pass("PMLEN=%d dereference\n", pmlen);
1217470b5afSSamuel Holland ++*p;
1227470b5afSSamuel Holland ksft_test_result_fail("PMLEN=%d invalid tag\n", pmlen);
1237470b5afSSamuel Holland }
1247470b5afSSamuel Holland
test_dereference(void)1257470b5afSSamuel Holland static void test_dereference(void)
1267470b5afSSamuel Holland {
1277470b5afSSamuel Holland ksft_print_msg("Testing userspace pointer dereference\n");
1287470b5afSSamuel Holland
1297470b5afSSamuel Holland signal(SIGSEGV, sigsegv_handler);
1307470b5afSSamuel Holland
1317470b5afSSamuel Holland test_dereference_pmlen(0);
1327470b5afSSamuel Holland test_dereference_pmlen(min_pmlen);
1337470b5afSSamuel Holland test_dereference_pmlen(max_pmlen);
1347470b5afSSamuel Holland
1357470b5afSSamuel Holland signal(SIGSEGV, SIG_DFL);
1367470b5afSSamuel Holland }
1377470b5afSSamuel Holland
execve_child_sigsegv_handler(int sig)1387470b5afSSamuel Holland static void execve_child_sigsegv_handler(int sig)
1397470b5afSSamuel Holland {
1407470b5afSSamuel Holland exit(42);
1417470b5afSSamuel Holland }
1427470b5afSSamuel Holland
execve_child(void)1437470b5afSSamuel Holland static int execve_child(void)
1447470b5afSSamuel Holland {
1457470b5afSSamuel Holland static volatile int i;
1467470b5afSSamuel Holland volatile int *p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - 7));
1477470b5afSSamuel Holland
1487470b5afSSamuel Holland signal(SIGSEGV, execve_child_sigsegv_handler);
1497470b5afSSamuel Holland
1507470b5afSSamuel Holland /* This dereference should raise SIGSEGV. */
1517470b5afSSamuel Holland return *p;
1527470b5afSSamuel Holland }
1537470b5afSSamuel Holland
test_fork_exec(void)1547470b5afSSamuel Holland static void test_fork_exec(void)
1557470b5afSSamuel Holland {
1567470b5afSSamuel Holland int ret, status;
1577470b5afSSamuel Holland
1587470b5afSSamuel Holland ksft_print_msg("Testing fork/exec behavior\n");
1597470b5afSSamuel Holland
1607470b5afSSamuel Holland ret = set_tagged_addr_ctrl(min_pmlen, false);
1617470b5afSSamuel Holland if (ret)
1627470b5afSSamuel Holland return ksft_test_result_error("setup (%d)\n", ret);
1637470b5afSSamuel Holland
1647470b5afSSamuel Holland if (fork()) {
1657470b5afSSamuel Holland wait(&status);
1667470b5afSSamuel Holland ksft_test_result(WIFEXITED(status) && WEXITSTATUS(status) == 42,
1677470b5afSSamuel Holland "dereference after fork\n");
1687470b5afSSamuel Holland } else {
1697470b5afSSamuel Holland static volatile int i = 42;
1707470b5afSSamuel Holland volatile int *p;
1717470b5afSSamuel Holland
1727470b5afSSamuel Holland p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - min_pmlen));
1737470b5afSSamuel Holland
1747470b5afSSamuel Holland /* This dereference should succeed. */
1757470b5afSSamuel Holland exit(*p);
1767470b5afSSamuel Holland }
1777470b5afSSamuel Holland
1787470b5afSSamuel Holland if (fork()) {
1797470b5afSSamuel Holland wait(&status);
1807470b5afSSamuel Holland ksft_test_result(WIFEXITED(status) && WEXITSTATUS(status) == 42,
1817470b5afSSamuel Holland "dereference after fork+exec\n");
1827470b5afSSamuel Holland } else {
1837470b5afSSamuel Holland /* Will call execve_child(). */
1847470b5afSSamuel Holland execve("/proc/self/exe", (char *const []) { "", NULL }, NULL);
1857470b5afSSamuel Holland }
1867470b5afSSamuel Holland }
1877470b5afSSamuel Holland
pwrite_wrapper(int fd,void * buf,size_t count,const char * msg)188*498d5b14SCharlie Jenkins static bool pwrite_wrapper(int fd, void *buf, size_t count, const char *msg)
189*498d5b14SCharlie Jenkins {
190*498d5b14SCharlie Jenkins int ret = pwrite(fd, buf, count, 0);
191*498d5b14SCharlie Jenkins
192*498d5b14SCharlie Jenkins if (ret != count) {
193*498d5b14SCharlie Jenkins ksft_perror(msg);
194*498d5b14SCharlie Jenkins return false;
195*498d5b14SCharlie Jenkins }
196*498d5b14SCharlie Jenkins return true;
197*498d5b14SCharlie Jenkins }
198*498d5b14SCharlie Jenkins
test_tagged_addr_abi_sysctl(void)1997470b5afSSamuel Holland static void test_tagged_addr_abi_sysctl(void)
2007470b5afSSamuel Holland {
201*498d5b14SCharlie Jenkins char *err_pwrite_msg = "failed to write to /proc/sys/abi/tagged_addr_disabled\n";
2027470b5afSSamuel Holland char value;
2037470b5afSSamuel Holland int fd;
2047470b5afSSamuel Holland
2057470b5afSSamuel Holland ksft_print_msg("Testing tagged address ABI sysctl\n");
2067470b5afSSamuel Holland
2077470b5afSSamuel Holland fd = open("/proc/sys/abi/tagged_addr_disabled", O_WRONLY);
2087470b5afSSamuel Holland if (fd < 0) {
2097470b5afSSamuel Holland ksft_test_result_skip("failed to open sysctl file\n");
2107470b5afSSamuel Holland ksft_test_result_skip("failed to open sysctl file\n");
2117470b5afSSamuel Holland return;
2127470b5afSSamuel Holland }
2137470b5afSSamuel Holland
2147470b5afSSamuel Holland value = '1';
215*498d5b14SCharlie Jenkins if (!pwrite_wrapper(fd, &value, 1, "write '1'"))
216*498d5b14SCharlie Jenkins ksft_test_result_fail(err_pwrite_msg);
217*498d5b14SCharlie Jenkins else
2187470b5afSSamuel Holland ksft_test_result(set_tagged_addr_ctrl(min_pmlen, true) == -EINVAL,
2197470b5afSSamuel Holland "sysctl disabled\n");
2207470b5afSSamuel Holland
2217470b5afSSamuel Holland value = '0';
222*498d5b14SCharlie Jenkins if (!pwrite_wrapper(fd, &value, 1, "write '0'"))
223*498d5b14SCharlie Jenkins ksft_test_result_fail(err_pwrite_msg);
224*498d5b14SCharlie Jenkins else
2257470b5afSSamuel Holland ksft_test_result(set_tagged_addr_ctrl(min_pmlen, true) == 0,
2267470b5afSSamuel Holland "sysctl enabled\n");
2277470b5afSSamuel Holland
2287470b5afSSamuel Holland set_tagged_addr_ctrl(0, false);
2297470b5afSSamuel Holland
2307470b5afSSamuel Holland close(fd);
2317470b5afSSamuel Holland }
2327470b5afSSamuel Holland
test_tagged_addr_abi_pmlen(int pmlen)2337470b5afSSamuel Holland static void test_tagged_addr_abi_pmlen(int pmlen)
2347470b5afSSamuel Holland {
2357470b5afSSamuel Holland int i, *p, ret;
2367470b5afSSamuel Holland
2377470b5afSSamuel Holland i = ~pmlen;
2387470b5afSSamuel Holland
2397470b5afSSamuel Holland if (pmlen) {
2407470b5afSSamuel Holland p = (int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen));
2417470b5afSSamuel Holland
2427470b5afSSamuel Holland ret = set_tagged_addr_ctrl(pmlen, false);
2437470b5afSSamuel Holland if (ret)
2447470b5afSSamuel Holland return ksft_test_result_error("PMLEN=%d ABI disabled setup (%d)\n",
2457470b5afSSamuel Holland pmlen, ret);
2467470b5afSSamuel Holland
2477470b5afSSamuel Holland ret = write(pipefd[1], p, sizeof(*p));
2487470b5afSSamuel Holland if (ret >= 0 || errno != EFAULT)
2497470b5afSSamuel Holland return ksft_test_result_fail("PMLEN=%d ABI disabled write\n", pmlen);
2507470b5afSSamuel Holland
2517470b5afSSamuel Holland ret = read(dev_zero, p, sizeof(*p));
2527470b5afSSamuel Holland if (ret >= 0 || errno != EFAULT)
2537470b5afSSamuel Holland return ksft_test_result_fail("PMLEN=%d ABI disabled read\n", pmlen);
2547470b5afSSamuel Holland
2557470b5afSSamuel Holland if (i != ~pmlen)
2567470b5afSSamuel Holland return ksft_test_result_fail("PMLEN=%d ABI disabled value\n", pmlen);
2577470b5afSSamuel Holland
2587470b5afSSamuel Holland ret = set_tagged_addr_ctrl(pmlen, true);
2597470b5afSSamuel Holland if (ret)
2607470b5afSSamuel Holland return ksft_test_result_error("PMLEN=%d ABI enabled setup (%d)\n",
2617470b5afSSamuel Holland pmlen, ret);
2627470b5afSSamuel Holland
2637470b5afSSamuel Holland ret = write(pipefd[1], p, sizeof(*p));
2647470b5afSSamuel Holland if (ret != sizeof(*p))
2657470b5afSSamuel Holland return ksft_test_result_fail("PMLEN=%d ABI enabled write\n", pmlen);
2667470b5afSSamuel Holland
2677470b5afSSamuel Holland ret = read(dev_zero, p, sizeof(*p));
2687470b5afSSamuel Holland if (ret != sizeof(*p))
2697470b5afSSamuel Holland return ksft_test_result_fail("PMLEN=%d ABI enabled read\n", pmlen);
2707470b5afSSamuel Holland
2717470b5afSSamuel Holland if (i)
2727470b5afSSamuel Holland return ksft_test_result_fail("PMLEN=%d ABI enabled value\n", pmlen);
2737470b5afSSamuel Holland
2747470b5afSSamuel Holland i = ~pmlen;
2757470b5afSSamuel Holland } else {
2767470b5afSSamuel Holland /* The tagged address ABI cannot be enabled when PMLEN == 0. */
2777470b5afSSamuel Holland ret = set_tagged_addr_ctrl(pmlen, true);
2787470b5afSSamuel Holland if (ret != -EINVAL)
2797470b5afSSamuel Holland return ksft_test_result_error("PMLEN=%d ABI setup (%d)\n",
2807470b5afSSamuel Holland pmlen, ret);
2817470b5afSSamuel Holland }
2827470b5afSSamuel Holland
2837470b5afSSamuel Holland p = (int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen - 1));
2847470b5afSSamuel Holland
2857470b5afSSamuel Holland ret = write(pipefd[1], p, sizeof(*p));
2867470b5afSSamuel Holland if (ret >= 0 || errno != EFAULT)
2877470b5afSSamuel Holland return ksft_test_result_fail("PMLEN=%d invalid tag write (%d)\n", pmlen, errno);
2887470b5afSSamuel Holland
2897470b5afSSamuel Holland ret = read(dev_zero, p, sizeof(*p));
2907470b5afSSamuel Holland if (ret >= 0 || errno != EFAULT)
2917470b5afSSamuel Holland return ksft_test_result_fail("PMLEN=%d invalid tag read\n", pmlen);
2927470b5afSSamuel Holland
2937470b5afSSamuel Holland if (i != ~pmlen)
2947470b5afSSamuel Holland return ksft_test_result_fail("PMLEN=%d invalid tag value\n", pmlen);
2957470b5afSSamuel Holland
2967470b5afSSamuel Holland ksft_test_result_pass("PMLEN=%d tagged address ABI\n", pmlen);
2977470b5afSSamuel Holland }
2987470b5afSSamuel Holland
test_tagged_addr_abi(void)2997470b5afSSamuel Holland static void test_tagged_addr_abi(void)
3007470b5afSSamuel Holland {
3017470b5afSSamuel Holland ksft_print_msg("Testing tagged address ABI\n");
3027470b5afSSamuel Holland
3037470b5afSSamuel Holland test_tagged_addr_abi_pmlen(0);
3047470b5afSSamuel Holland test_tagged_addr_abi_pmlen(min_pmlen);
3057470b5afSSamuel Holland test_tagged_addr_abi_pmlen(max_pmlen);
3067470b5afSSamuel Holland }
3077470b5afSSamuel Holland
3087470b5afSSamuel Holland static struct test_info {
3097470b5afSSamuel Holland unsigned int nr_tests;
3107470b5afSSamuel Holland void (*test_fn)(void);
3117470b5afSSamuel Holland } tests[] = {
3127470b5afSSamuel Holland { .nr_tests = 17 * 3, test_pmlen },
3137470b5afSSamuel Holland { .nr_tests = 3, test_dereference },
3147470b5afSSamuel Holland { .nr_tests = 2, test_fork_exec },
3157470b5afSSamuel Holland { .nr_tests = 2, test_tagged_addr_abi_sysctl },
3167470b5afSSamuel Holland { .nr_tests = 3, test_tagged_addr_abi },
3177470b5afSSamuel Holland };
3187470b5afSSamuel Holland
main(int argc,char ** argv)3197470b5afSSamuel Holland int main(int argc, char **argv)
3207470b5afSSamuel Holland {
3217470b5afSSamuel Holland unsigned int plan = 0;
3227470b5afSSamuel Holland int ret;
3237470b5afSSamuel Holland
3247470b5afSSamuel Holland /* Check if this is the child process after execve(). */
3257470b5afSSamuel Holland if (!argv[0][0])
3267470b5afSSamuel Holland return execve_child();
3277470b5afSSamuel Holland
3287470b5afSSamuel Holland dev_zero = open("/dev/zero", O_RDWR);
3297470b5afSSamuel Holland if (dev_zero < 0)
3307470b5afSSamuel Holland return 1;
3317470b5afSSamuel Holland
3327470b5afSSamuel Holland /* Write to a pipe so the kernel must dereference the buffer pointer. */
3337470b5afSSamuel Holland ret = pipe(pipefd);
3347470b5afSSamuel Holland if (ret)
3357470b5afSSamuel Holland return 1;
3367470b5afSSamuel Holland
3377470b5afSSamuel Holland ksft_print_header();
3387470b5afSSamuel Holland
3397470b5afSSamuel Holland for (int i = 0; i < ARRAY_SIZE(tests); i++)
3407470b5afSSamuel Holland plan += tests[i].nr_tests;
3417470b5afSSamuel Holland
3427470b5afSSamuel Holland ksft_set_plan(plan);
3437470b5afSSamuel Holland
3447470b5afSSamuel Holland for (int i = 0; i < ARRAY_SIZE(tests); i++)
3457470b5afSSamuel Holland tests[i].test_fn();
3467470b5afSSamuel Holland
3477470b5afSSamuel Holland ksft_finished();
3487470b5afSSamuel Holland }
349