Re: [PATCH] selftests: vDSO: ensure vgetrandom works in a time namespace

From: Christophe Leroy
Date: Mon Sep 09 2024 - 06:42:17 EST


Hi Jason,

Le 05/09/2024 à 19:31, Jason A. Donenfeld a écrit :
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 implies that the RNG appears initialized, 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 vgetrandom
doesn't get called. If it doesn't get called, then the test succeeds.

Signed-off-by: Jason A. Donenfeld <Jason@xxxxxxxxx>
---
.../selftests/vDSO/vdso_test_getrandom.c | 41 ++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/vDSO/vdso_test_getrandom.c b/tools/testing/selftests/vDSO/vdso_test_getrandom.c
index 8866b65a4605..dfda5061f454 100644
--- a/tools/testing/selftests/vDSO/vdso_test_getrandom.c
+++ b/tools/testing/selftests/vDSO/vdso_test_getrandom.c
@@ -16,8 +16,11 @@
#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/ptrace.h>
#include "../kselftest.h"
#include "parse_vdso.h"
@@ -239,9 +242,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 +254,41 @@ static void kselftest(void)
}
ksft_test_result_pass("getrandom: PASS\n");
+
+ assert(unshare(CLONE_NEWUSER) == 0 && unshare(CLONE_NEWTIME) == 0);

~# ./vdso_test_getrandom
TAP version 13
1..2
ok 1 getrandom: PASS
vdso_test_getrandom: vdso_test_getrandom.c:276: kselftest: Assertion `ret == 0' failed.
Aborted

That's too strong. When unshare() returns EINVAL it means the kernel is not built with CONFIG_TIME_NS. In that case the test should be SKIPPED.

And when unshare() returns EPERM, it means the user is not authorised to use unshare(), that's an expected error that shouldn't lead to an assert either, instead it should gracefully says FAILED I think.

+ 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.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);
}