diff mbox series

[RFC,v2,08/10] selftests: clone3: add CHECK_FIELDS selftests

Message ID 20240906-extensible-structs-check_fields-v2-8-0f46d2de9bad@cyphar.com
State New
Headers show
Series extensible syscalls: CHECK_FIELDS to allow for easier feature detection | expand

Commit Message

Aleksa Sarai Sept. 5, 2024, 2:56 p.m. UTC
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
---
 tools/testing/selftests/clone3/.gitignore          |   1 +
 tools/testing/selftests/clone3/Makefile            |   4 +-
 .../testing/selftests/clone3/clone3_check_fields.c | 264 +++++++++++++++++++++
 3 files changed, 267 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/tools/testing/selftests/clone3/.gitignore b/tools/testing/selftests/clone3/.gitignore
index 83c0f6246055..4ec3e1ecd273 100644
--- a/tools/testing/selftests/clone3/.gitignore
+++ b/tools/testing/selftests/clone3/.gitignore
@@ -3,3 +3,4 @@  clone3
 clone3_clear_sighand
 clone3_set_tid
 clone3_cap_checkpoint_restore
+clone3_check_fields
diff --git a/tools/testing/selftests/clone3/Makefile b/tools/testing/selftests/clone3/Makefile
index 84832c369a2e..37141ca13f7c 100644
--- a/tools/testing/selftests/clone3/Makefile
+++ b/tools/testing/selftests/clone3/Makefile
@@ -1,8 +1,8 @@ 
 # SPDX-License-Identifier: GPL-2.0
-CFLAGS += -g -std=gnu99 $(KHDR_INCLUDES)
+CFLAGS += -g -std=gnu99 $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
 LDLIBS += -lcap
 
 TEST_GEN_PROGS := clone3 clone3_clear_sighand clone3_set_tid \
-	clone3_cap_checkpoint_restore
+	clone3_cap_checkpoint_restore clone3_check_fields
 
 include ../lib.mk
diff --git a/tools/testing/selftests/clone3/clone3_check_fields.c b/tools/testing/selftests/clone3/clone3_check_fields.c
new file mode 100644
index 000000000000..477604f9a3fb
--- /dev/null
+++ b/tools/testing/selftests/clone3/clone3_check_fields.c
@@ -0,0 +1,264 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author: Aleksa Sarai <cyphar@cyphar.com>
+ * Copyright (C) 2024 SUSE LLC
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <inttypes.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sched.h>
+
+#include "../kselftest.h"
+#include "clone3_selftests.h"
+
+#ifndef CHECK_FIELDS
+#define CHECK_FIELDS (1ULL << 63)
+#endif
+
+struct __clone_args_v0 {
+	__aligned_u64 flags;
+	__aligned_u64 pidfd;
+	__aligned_u64 child_tid;
+	__aligned_u64 parent_tid;
+	__aligned_u64 exit_signal;
+	__aligned_u64 stack;
+	__aligned_u64 stack_size;
+	__aligned_u64 tls;
+};
+
+struct __clone_args_v1 {
+	__aligned_u64 flags;
+	__aligned_u64 pidfd;
+	__aligned_u64 child_tid;
+	__aligned_u64 parent_tid;
+	__aligned_u64 exit_signal;
+	__aligned_u64 stack;
+	__aligned_u64 stack_size;
+	__aligned_u64 tls;
+	__aligned_u64 set_tid;
+	__aligned_u64 set_tid_size;
+};
+
+struct __clone_args_v2 {
+	__aligned_u64 flags;
+	__aligned_u64 pidfd;
+	__aligned_u64 child_tid;
+	__aligned_u64 parent_tid;
+	__aligned_u64 exit_signal;
+	__aligned_u64 stack;
+	__aligned_u64 stack_size;
+	__aligned_u64 tls;
+	__aligned_u64 set_tid;
+	__aligned_u64 set_tid_size;
+	__aligned_u64 cgroup;
+};
+
+static int call_clone3(void *clone_args, size_t size)
+{
+	int status;
+	pid_t pid;
+
+	pid = sys_clone3(clone_args, size);
+	if (pid < 0)
+		return -errno;
+
+	if (pid == 0) {
+		ksft_print_msg("I am the child, my PID is %d\n", getpid());
+		_exit(EXIT_SUCCESS);
+	}
+
+	ksft_print_msg("I am the parent (%d). My child's pid is %d\n",
+			getpid(), pid);
+
+	if (waitpid(-1, &status, __WALL) < 0) {
+		ksft_print_msg("waitpid() returned %s\n", strerror(errno));
+		return -errno;
+	}
+	if (!WIFEXITED(status)) {
+		ksft_print_msg("Child did not exit normally, status 0x%x\n",
+			       status);
+		return EXIT_FAILURE;
+	}
+	if (WEXITSTATUS(status))
+		return WEXITSTATUS(status);
+
+	return 0;
+}
+
+static bool check(bool *failed, bool pred)
+{
+	*failed |= pred;
+	return pred;
+}
+
+static void test_clone3_check_fields(const char *test_name, size_t struct_size)
+{
+	size_t bufsize;
+	void *buffer;
+	pid_t pid;
+	bool failed = false;
+	void (*resultfn)(const char *msg, ...) = ksft_test_result_pass;
+
+	/* Allocate some bytes after clone_args to verify they are cleared. */
+	bufsize = struct_size + 16;
+	buffer = malloc(bufsize);
+	/* Set the structure to dummy values. */
+	memset(buffer, 0, bufsize);
+	memset(buffer, 0xAB, struct_size);
+
+	pid = call_clone3(buffer, CHECK_FIELDS | struct_size);
+	if (check(&failed, (pid != -EEXTSYS_NOOP)))
+		ksft_print_msg("clone3(CHECK_FIELDS) returned the wrong error code: %d (%s) != %d\n",
+			       pid, strerror(-pid), -EEXTSYS_NOOP);
+
+	switch (struct_size) {
+	case sizeof(struct __clone_args_v2): {
+		struct __clone_args_v2 *args = buffer;
+
+		if (check(&failed, (args->cgroup != 0xFFFFFFFFFFFFFFFF)))
+			ksft_print_msg("clone3(CHECK_FIELDS) has wrong cgroup field: 0x%.16llx != 0x%.16llx\n",
+				       args->cgroup, 0xFFFFFFFFFFFFFFFF);
+
+		/* fallthrough; */
+	}
+	case sizeof(struct __clone_args_v1): {
+		struct __clone_args_v1 *args = buffer;
+
+		if (check(&failed, (args->set_tid != 0xFFFFFFFFFFFFFFFF)))
+			ksft_print_msg("clone3(CHECK_FIELDS) has wrong set_tid field: 0x%.16llx != 0x%.16llx\n",
+				       args->set_tid, 0xFFFFFFFFFFFFFFFF);
+		if (check(&failed, (args->set_tid_size != 0xFFFFFFFFFFFFFFFF)))
+			ksft_print_msg("clone3(CHECK_FIELDS) has wrong set_tid_size field: 0x%.16llx != 0x%.16llx\n",
+				       args->set_tid_size, 0xFFFFFFFFFFFFFFFF);
+
+		/* fallthrough; */
+	}
+	case sizeof(struct __clone_args_v0): {
+		struct __clone_args_v0 *args = buffer;
+
+		if (check(&failed, !(args->flags & CLONE_NEWUSER)))
+			ksft_print_msg("clone3(CHECK_FIELDS) is missing CLONE_NEWUSER in flags: 0x%.16llx (0x%.16llx)\n",
+				       args->flags, CLONE_NEWUSER);
+		if (check(&failed, !(args->flags & CLONE_THREAD)))
+			ksft_print_msg("clone3(CHECK_FIELDS) is missing CLONE_THREAD in flags: 0x%.16llx (0x%.16llx)\n",
+				       args->flags, CLONE_THREAD);
+		/*
+		 * CLONE_INTO_CGROUP was added in v2, but it will be set even
+		 * with smaller structure sizes.
+		 */
+		if (check(&failed, !(args->flags & CLONE_INTO_CGROUP)))
+			ksft_print_msg("clone3(CHECK_FIELDS) is missing CLONE_INTO_CGROUP in flags: 0x%.16llx (0x%.16llx)\n",
+				       args->flags, CLONE_INTO_CGROUP);
+
+		if (check(&failed, (args->exit_signal != 0xFF)))
+			ksft_print_msg("clone3(CHECK_FIELDS) has wrong exit_signal field: 0x%.16llx != 0x%.16llx\n",
+				       args->exit_signal, 0xFF);
+
+		if (check(&failed, (args->stack != 0xFFFFFFFFFFFFFFFF)))
+			ksft_print_msg("clone3(CHECK_FIELDS) has wrong stack field: 0x%.16llx != 0x%.16llx\n",
+				       args->stack, 0xFFFFFFFFFFFFFFFF);
+		if (check(&failed, (args->stack_size != 0xFFFFFFFFFFFFFFFF)))
+			ksft_print_msg("clone3(CHECK_FIELDS) has wrong stack_size field: 0x%.16llx != 0x%.16llx\n",
+				       args->stack_size, 0xFFFFFFFFFFFFFFFF);
+		if (check(&failed, (args->tls != 0xFFFFFFFFFFFFFFFF)))
+			ksft_print_msg("clone3(CHECK_FIELDS) has wrong tls field: 0x%.16llx != 0x%.16llx\n",
+				       args->tls, 0xFFFFFFFFFFFFFFFF);
+
+		break;
+	}
+	default:
+		fprintf(stderr, "INVALID STRUCTURE SIZE: %d\n", struct_size);
+		abort();
+	}
+
+	/* Verify that the trailing parts of the buffer are still 0. */
+	for (size_t i = struct_size; i < bufsize; i++) {
+		char ch = ((char *)buffer)[i];
+		if (check(&failed, (ch != '\x00')))
+			ksft_print_msg("clone3(CHECK_FIELDS) touched a byte outside the size: buffer[%d] = 0x%.2x\n",
+				       i, ch);
+	}
+
+	if (failed)
+		resultfn = ksft_test_result_fail;
+
+	resultfn("clone3(CHECK_FIELDS) with %s\n", test_name);
+	free(buffer);
+}
+
+struct check_fields_test {
+	const char *name;
+	size_t struct_size;
+};
+
+static struct check_fields_test check_fields_tests[] = {
+	{"struct v0", sizeof(struct __clone_args_v0)},
+	{"struct v1", sizeof(struct __clone_args_v1)},
+	{"struct v2", sizeof(struct __clone_args_v2)},
+};
+
+static int test_clone3_check_fields_badsize(const char *test_name,
+					    size_t struct_size)
+{
+	void *buffer;
+	pid_t pid;
+	bool failed = false;
+	int expected_err;
+	void (*resultfn)(const char *msg, ...) = ksft_test_result_pass;
+
+	buffer = malloc(struct_size);
+	memset(buffer, 0xAB, struct_size);
+
+	if (struct_size < sizeof(struct __clone_args_v0))
+		expected_err = -EINVAL;
+	if (struct_size > 4096)
+		expected_err = -E2BIG;
+
+	pid = call_clone3(buffer, CHECK_FIELDS | struct_size);
+	if (check(&failed, (pid != expected_err)))
+		ksft_print_msg("clone3(CHECK_FIELDS) returned the wrong error code: %d (%s) != %d (%s)\n",
+			       pid, strerror(-pid),
+			       expected_err, strerror(-expected_err));
+
+	if (failed)
+		resultfn = ksft_test_result_fail;
+
+	resultfn("clone3(CHECK_FIELDS) with %s returns %d (%s)\n", test_name,
+		 expected_err, strerror(-expected_err));
+	free(buffer);
+}
+
+static struct check_fields_test bad_size_tests[] = {
+	{"short struct (< v0)", 1},
+	{"long struct (> PAGE_SIZE)", 0xF000},
+};
+
+int main(void)
+{
+	ksft_print_header();
+	ksft_set_plan(ARRAY_SIZE(check_fields_tests) + ARRAY_SIZE(bad_size_tests));
+	test_clone3_supported();
+
+	for (int i = 0; i < ARRAY_SIZE(check_fields_tests); i++) {
+		struct check_fields_test *test = &check_fields_tests[i];
+		test_clone3_check_fields(test->name, test->struct_size);
+	}
+	for (int i = 0; i < ARRAY_SIZE(bad_size_tests); i++) {
+		struct check_fields_test *test = &bad_size_tests[i];
+		test_clone3_check_fields_badsize(test->name, test->struct_size);
+	}
+
+	ksft_finished();
+}