]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests/rseq: Add rseq syscall errors test
authorMichael Jeanson <mjeanson@efficios.com>
Tue, 21 Jan 2025 21:33:52 +0000 (16:33 -0500)
committerIngo Molnar <mingo@kernel.org>
Sat, 22 Feb 2025 13:13:45 +0000 (14:13 +0100)
This test adds coverage of expected errors during rseq registration and
unregistration, it disables glibc integration and will thus always
exercise the rseq syscall explictly.

Signed-off-by: Michael Jeanson <mjeanson@efficios.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Shuah Khan <skhan@linuxfoundation.org>
Link: https://lore.kernel.org/r/20250121213402.1754762-1-mjeanson@efficios.com
tools/testing/selftests/rseq/.gitignore
tools/testing/selftests/rseq/Makefile
tools/testing/selftests/rseq/rseq.c
tools/testing/selftests/rseq/rseq.h
tools/testing/selftests/rseq/run_syscall_errors_test.sh [new file with mode: 0755]
tools/testing/selftests/rseq/syscall_errors_test.c [new file with mode: 0644]

index 16496de5f6ce48a53f1836d4b220ba2e99fd0814..0fda241fa62b0295feef918a97f75061021f39c1 100644 (file)
@@ -9,3 +9,4 @@ param_test_compare_twice
 param_test_mm_cid
 param_test_mm_cid_benchmark
 param_test_mm_cid_compare_twice
+syscall_errors_test
index 5a3432fceb58671a2383a0385bee5ff0e11c0a09..0d0a5fae59547fead6c543a7cfef9af38839f009 100644 (file)
@@ -16,11 +16,12 @@ OVERRIDE_TARGETS = 1
 
 TEST_GEN_PROGS = basic_test basic_percpu_ops_test basic_percpu_ops_mm_cid_test param_test \
                param_test_benchmark param_test_compare_twice param_test_mm_cid \
-               param_test_mm_cid_benchmark param_test_mm_cid_compare_twice
+               param_test_mm_cid_benchmark param_test_mm_cid_compare_twice \
+               syscall_errors_test
 
 TEST_GEN_PROGS_EXTENDED = librseq.so
 
-TEST_PROGS = run_param_test.sh
+TEST_PROGS = run_param_test.sh run_syscall_errors_test.sh
 
 TEST_FILES := settings
 
@@ -54,3 +55,7 @@ $(OUTPUT)/param_test_mm_cid_benchmark: param_test.c $(TEST_GEN_PROGS_EXTENDED) \
 $(OUTPUT)/param_test_mm_cid_compare_twice: param_test.c $(TEST_GEN_PROGS_EXTENDED) \
                                        rseq.h rseq-*.h
        $(CC) $(CFLAGS) -DBUILDOPT_RSEQ_PERCPU_MM_CID -DRSEQ_COMPARE_TWICE $< $(LDLIBS) -lrseq -o $@
+
+$(OUTPUT)/syscall_errors_test: syscall_errors_test.c $(TEST_GEN_PROGS_EXTENDED) \
+                                       rseq.h rseq-*.h
+       $(CC) $(CFLAGS) $< $(LDLIBS) -lrseq -o $@
index f6156790c3b4dfaf85e7e196656fe1bf76539411..1e29db92094d8e5d505c5798caea5f30f2d6a231 100644 (file)
@@ -87,7 +87,7 @@ static int sys_getcpu(unsigned *cpu, unsigned *node)
        return syscall(__NR_getcpu, cpu, node, NULL);
 }
 
-int rseq_available(void)
+bool rseq_available(void)
 {
        int rc;
 
@@ -96,9 +96,9 @@ int rseq_available(void)
                abort();
        switch (errno) {
        case ENOSYS:
-               return 0;
+               return false;
        case EINVAL:
-               return 1;
+               return true;
        default:
                abort();
        }
index ba424ce80a719f1d3725a59314371ccab889d2d5..f51a5fdb044431585cbc57b6ae07fa2f59aefe23 100644 (file)
@@ -159,6 +159,11 @@ int32_t rseq_fallback_current_cpu(void);
  */
 int32_t rseq_fallback_current_node(void);
 
+/*
+ * Returns true if rseq is supported.
+ */
+bool rseq_available(void);
+
 /*
  * Values returned can be either the current CPU number, -1 (rseq is
  * uninitialized), or -2 (rseq initialization has failed).
diff --git a/tools/testing/selftests/rseq/run_syscall_errors_test.sh b/tools/testing/selftests/rseq/run_syscall_errors_test.sh
new file mode 100755 (executable)
index 0000000..9272246
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/bash
+# SPDX-License-Identifier: MIT
+# SPDX-FileCopyrightText: 2024 Michael Jeanson <mjeanson@efficios.com>
+
+GLIBC_TUNABLES="${GLIBC_TUNABLES:-}:glibc.pthread.rseq=0" ./syscall_errors_test
diff --git a/tools/testing/selftests/rseq/syscall_errors_test.c b/tools/testing/selftests/rseq/syscall_errors_test.c
new file mode 100644 (file)
index 0000000..a5d9e1f
--- /dev/null
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: 2024 Michael Jeanson <mjeanson@efficios.com>
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <assert.h>
+#include <stdint.h>
+#include <syscall.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "rseq.h"
+
+static int sys_rseq(void *rseq_abi, uint32_t rseq_len,
+                   int flags, uint32_t sig)
+{
+       return syscall(__NR_rseq, rseq_abi, rseq_len, flags, sig);
+}
+
+/*
+ * Check the value of errno on some expected failures of the rseq syscall.
+ */
+
+int main(void)
+{
+       struct rseq_abi *global_rseq = rseq_get_abi();
+       int ret;
+       int errno_copy;
+
+       if (!rseq_available()) {
+               fprintf(stderr, "rseq syscall unavailable");
+               goto error;
+       }
+
+       /* The current thread is NOT registered. */
+
+       /* EINVAL */
+       errno = 0;
+       ret = sys_rseq(global_rseq, 32, -1, RSEQ_SIG);
+       errno_copy = errno;
+       fprintf(stderr, "Registration with invalid flag fails with errno set to EINVAL (ret = %d, errno = %s)\n", ret, strerrorname_np(errno_copy));
+       if (ret == 0 || errno_copy != EINVAL)
+               goto error;
+
+       errno = 0;
+       ret = sys_rseq((char *) global_rseq + 1, 32, 0, RSEQ_SIG);
+       errno_copy = errno;
+       fprintf(stderr, "Registration with unaligned rseq_abi fails with errno set to EINVAL (ret = %d, errno = %s)\n", ret, strerrorname_np(errno_copy));
+       if (ret == 0 || errno_copy != EINVAL)
+               goto error;
+
+       errno = 0;
+       ret = sys_rseq(global_rseq, 31, 0, RSEQ_SIG);
+       errno_copy = errno;
+       fprintf(stderr, "Registration with invalid size fails with errno set to EINVAL (ret = %d, errno = %s)\n", ret, strerrorname_np(errno_copy));
+       if (ret == 0 || errno_copy != EINVAL)
+               goto error;
+
+
+#if defined(__LP64__) && (!defined(__s390__) && !defined(__s390x__))
+       /*
+        * We haven't found a reliable way to find an invalid address when
+        * running a 32bit userspace on a 64bit kernel, so only run this test
+        * on 64bit builds for the moment.
+        *
+        * Also exclude architectures that select
+        * CONFIG_ALTERNATE_USER_ADDRESS_SPACE where the kernel and userspace
+        * have their own address space and this failure can't happen.
+        */
+
+       /* EFAULT */
+       errno = 0;
+       ret = sys_rseq((void *) -4096UL, 32, 0, RSEQ_SIG);
+       errno_copy = errno;
+       fprintf(stderr, "Registration with invalid address fails with errno set to EFAULT (ret = %d, errno = %s)\n", ret, strerrorname_np(errno_copy));
+       if (ret == 0 || errno_copy != EFAULT)
+               goto error;
+#endif
+
+       errno = 0;
+       ret = sys_rseq(global_rseq, 32, 0, RSEQ_SIG);
+       errno_copy = errno;
+       fprintf(stderr, "Registration succeeds for the current thread (ret = %d, errno = %s)\n", ret, strerrorname_np(errno_copy));
+       if (ret != 0 && errno != 0)
+               goto error;
+
+       /* The current thread is registered. */
+
+       /* EBUSY */
+       errno = 0;
+       ret = sys_rseq(global_rseq, 32, 0, RSEQ_SIG);
+       errno_copy = errno;
+       fprintf(stderr, "Double registration fails with errno set to EBUSY (ret = %d, errno = %s)\n", ret, strerrorname_np(errno_copy));
+       if (ret == 0 || errno_copy != EBUSY)
+               goto error;
+
+       /* EPERM */
+       errno = 0;
+       ret = sys_rseq(global_rseq, 32, RSEQ_ABI_FLAG_UNREGISTER, RSEQ_SIG + 1);
+       errno_copy = errno;
+       fprintf(stderr, "Unregistration with wrong RSEQ_SIG fails with errno to EPERM (ret = %d, errno = %s)\n", ret, strerrorname_np(errno_copy));
+       if (ret == 0 || errno_copy != EPERM)
+               goto error;
+
+       errno = 0;
+       ret = sys_rseq(global_rseq, 32, RSEQ_ABI_FLAG_UNREGISTER, RSEQ_SIG);
+       errno_copy = errno;
+       fprintf(stderr, "Unregistration succeeds for the current thread (ret = %d, errno = %s)\n", ret, strerrorname_np(errno_copy));
+       if (ret != 0)
+               goto error;
+
+       errno = 0;
+       ret = sys_rseq(global_rseq, 32, RSEQ_ABI_FLAG_UNREGISTER, RSEQ_SIG);
+       errno_copy = errno;
+       fprintf(stderr, "Double unregistration fails with errno set to EINVAL (ret = %d, errno = %s)\n", ret, strerrorname_np(errno_copy));
+       if (ret == 0 || errno_copy != EINVAL)
+               goto error;
+
+       return 0;
+error:
+       return -1;
+}