]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests: vDSO: ensure vgetrandom works in a time namespace
authorJason A. Donenfeld <Jason@zx2c4.com>
Thu, 5 Sep 2024 17:17:24 +0000 (19:17 +0200)
committerJason A. Donenfeld <Jason@zx2c4.com>
Fri, 13 Sep 2024 15:28:35 +0000 (17:28 +0200)
After verifying that vDSO getrandom does work, which ensures that the
RNG is initialized, test to see if it also works inside of a time
namespace. This is important to test, because the vvar pages get
swizzled around there. If the arch code isn't careful, the RNG will
appear uninitialized inside of a time namespace.

Because broken code makes the RNG appear uninitialized, test that
everything works by issuing a call to vgetrandom from a fork in a time
namespace, and use ptrace to ensure that the actual syscall getrandom
doesn't get called. If it doesn't get called, then the test succeeds.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
tools/testing/selftests/vDSO/vdso_test_getrandom.c

index 8866b65a46055e016bd78a03976a72aac2cee253..72a1d9b43a84445f3f45e599201cac30ee021a7a 100644 (file)
 #include <sys/mman.h>
 #include <sys/random.h>
 #include <sys/syscall.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
 #include <sys/types.h>
 #include <linux/random.h>
+#include <linux/compiler.h>
+#include <linux/ptrace.h>
 
 #include "../kselftest.h"
 #include "parse_vdso.h"
@@ -239,9 +243,10 @@ static void fill(void)
 static void kselftest(void)
 {
        uint8_t weird_size[1263];
+       pid_t child;
 
        ksft_print_header();
-       ksft_set_plan(1);
+       ksft_set_plan(2);
 
        for (size_t i = 0; i < 1000; ++i) {
                ssize_t ret = vgetrandom(weird_size, sizeof(weird_size), 0);
@@ -250,6 +255,42 @@ static void kselftest(void)
        }
 
        ksft_test_result_pass("getrandom: PASS\n");
+
+       unshare(CLONE_NEWUSER);
+       assert(unshare(CLONE_NEWTIME) == 0);
+       child = fork();
+       assert(child >= 0);
+       if (!child) {
+               vgetrandom_init();
+               child = getpid();
+               assert(ptrace(PTRACE_TRACEME, 0, NULL, NULL) == 0);
+               assert(kill(child, SIGSTOP) == 0);
+               assert(vgetrandom(weird_size, sizeof(weird_size), 0) == sizeof(weird_size));
+               _exit(0);
+       }
+       for (;;) {
+               struct ptrace_syscall_info info = { 0 };
+               int status, ret;
+               assert(waitpid(child, &status, 0) >= 0);
+               if (WIFEXITED(status)) {
+                       if (WEXITSTATUS(status) != 0)
+                               exit(KSFT_FAIL);
+                       break;
+               }
+               assert(WIFSTOPPED(status));
+               if (WSTOPSIG(status) == SIGSTOP)
+                       assert(ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD) == 0);
+               else if (WSTOPSIG(status) == (SIGTRAP | 0x80)) {
+                       assert(ptrace(PTRACE_GET_SYSCALL_INFO, child, sizeof(info), &info) > 0);
+                       if (info.op == PTRACE_SYSCALL_INFO_ENTRY && info.entry.nr == __NR_getrandom &&
+                           info.entry.args[0] == (uintptr_t)weird_size && info.entry.args[1] == sizeof(weird_size))
+                               exit(KSFT_FAIL);
+               }
+               assert(ptrace(PTRACE_SYSCALL, child, 0, 0) == 0);
+       }
+
+       ksft_test_result_pass("getrandom timens: PASS\n");
+
        exit(KSFT_PASS);
 }