new file mode 100644
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * KUnit Userspace testing API.
+ *
+ * Copyright (C) 2025, Linuxtronix GmbH.
+ * Author: Thomas Weißschuh <thomas.weissschuh@linutronix.de>
+ */
+
+#ifndef _KUNIT_UAPI_H
+#define _KUNIT_UAPI_H
+
+struct blob;
+struct kunit;
+
+void kunit_uapi_run_executable(struct kunit *test, const struct blob *blob);
+
+#endif /* _KUNIT_UAPI_H */
@@ -15,6 +15,15 @@ menuconfig KUNIT
if KUNIT
+config KUNIT_UAPI
+ def_bool y
+ depends on CC_CAN_LINK_STATIC || ARCH_HAS_NOLIBC
+ select HEADERS_INSTALL
+ select TMPFS
+ help
+ Enables support for build and running userspace selftests as part of kunit.
+ These tests should use kselftest.h for status reporting.
+
config KUNIT_DEBUGFS
bool "KUnit - Enable /sys/kernel/debug/kunit debugfs representation" if !KUNIT_ALL_TESTS
default KUNIT_ALL_TESTS
@@ -12,6 +12,8 @@ kunit-objs += test.o \
device.o \
platform.o
+obj-$(CONFIG_KUNIT_UAPI) += uapi.o
+
ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
kunit-objs += debugfs.o
endif
new file mode 100644
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit Userspace testing API.
+ *
+ * Copyright (C) 2025, Linuxtronix GmbH.
+ * Author: Thomas Weißschuh <thomas.weissschuh@linutronix.de>
+ */
+
+#include <linux/binfmts.h>
+#include <linux/blob.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/fs_struct.h>
+#include <linux/pid.h>
+#include <linux/pipe_fs_i.h>
+#include <linux/sched/task.h>
+#include <linux/types.h>
+
+#include <kunit/test.h>
+#include <kunit/uapi.h>
+
+static struct vfsmount *kunit_uapi_mount_tmpfs(void)
+{
+ struct file_system_type *type;
+ struct vfsmount *mnt;
+
+ type = get_fs_type("tmpfs");
+ if (!type)
+ return ERR_PTR(-ENODEV);
+
+ /* FIXME
+ * The mount setup is supposed to look like this:
+ * kunit_uapi_mount_tmpfs() sets up a private mount,
+ * with nothing visible except the new tmpfs.
+ * Then each executable execution gets a new namespace on top of that
+ * on which it can mount whatever it needs.
+ * However I didn't manage to set this up, so keep everything simple
+ * for now and let somebody familiar with the VFS figure this out.
+ */
+
+ mnt = kern_mount(type);
+ put_filesystem(type);
+
+ return mnt;
+}
+
+static int kunit_uapi_write_file(struct vfsmount *mnt, const char *name, mode_t mode,
+ const u8 *data, size_t size)
+{
+ struct file *file;
+ ssize_t written;
+
+ file = file_open_root_mnt(mnt, name, O_CREAT | O_WRONLY, mode);
+ if (IS_ERR(file))
+ return PTR_ERR(file);
+
+ written = kernel_write(file, data, size, NULL);
+ filp_close(file, NULL);
+ if (written != size) {
+ if (written >= 0)
+ return -ENOMEM;
+ return written;
+ }
+
+ /* Flush delayed fput so exec can open the file read-only */
+ flush_delayed_fput();
+
+ return 0;
+}
+
+struct kunit_uapi_user_mode_thread_ctx {
+ const char *executable;
+
+ /* Signals mnt, out, pwd and tgid */
+ struct completion setup_done;
+ struct vfsmount *mnt;
+ struct file *out;
+ struct path pwd;
+ pid_t tgid;
+
+ /* Valid after wait(tgid) */
+ int exec_err;
+};
+
+static int kunit_uapi_user_mode_thread_init(void *data)
+{
+ struct kunit_uapi_user_mode_thread_ctx *ctx = data;
+ const char *const argv[] = {
+ ctx->executable,
+ NULL
+ };
+ struct file *out[2];
+ int err;
+
+ err = create_pipe_files(out, 0);
+ if (err)
+ return err;
+
+ err = replace_fd(1, out[1], 0);
+ if (err < 0) {
+ fput(out[1]);
+ return err;
+ }
+
+ err = replace_fd(2, out[1], 0);
+ if (err < 0) {
+ replace_fd(0, NULL, 0);
+ fput(out[1]);
+ return err;
+ }
+
+ fput(out[1]);
+
+ ctx->out = out[0];
+ ctx->tgid = current->tgid;
+
+ set_fs_pwd(current->fs, &ctx->pwd);
+ kernel_sigaction(SIGKILL, SIG_DFL);
+
+ complete(&ctx->setup_done);
+ ctx->exec_err = kernel_execve(ctx->executable, argv, NULL);
+ if (!ctx->exec_err)
+ return 0;
+ do_exit(0);
+}
+
+static size_t kunit_uapi_printk_subtest_lines(char *buf, size_t s)
+{
+ const char *ptr = buf, *newline;
+ size_t n;
+
+ while (s) {
+ newline = strnchr(ptr, s, '\n');
+ if (!newline)
+ break;
+
+ n = newline - ptr + 1;
+
+ pr_info(KUNIT_SUBSUBTEST_INDENT "%.*s", (int)n, ptr);
+ ptr += n;
+ s -= n;
+ }
+
+ memmove(buf, ptr, s);
+
+ return s;
+}
+
+static int kunit_uapi_forward_to_printk(struct file *output)
+{
+ char buf[512];
+ size_t s = 0;
+ ssize_t n;
+
+ while (1) {
+ n = kernel_read(output, buf + s, sizeof(buf) - s, NULL);
+ if (n <= 0)
+ return n;
+ s = kunit_uapi_printk_subtest_lines(buf, s + n);
+ }
+}
+
+static void kunit_uapi_kill_pid(pid_t pid)
+{
+ struct pid *p;
+
+ p = find_get_pid(pid);
+ kill_pid(p, SIGKILL, 1);
+ put_pid(p);
+}
+
+static int kunit_uapi_run_executable_in_mount(struct kunit *test, const char *executable,
+ struct vfsmount *mnt)
+{
+ struct kunit_uapi_user_mode_thread_ctx ctx = {
+ .setup_done = COMPLETION_INITIALIZER_ONSTACK(ctx.setup_done),
+ .executable = executable,
+ .pwd = {
+ .mnt = mnt,
+ .dentry = mnt->mnt_root,
+ },
+ };
+ int forward_err, wait_err, ret;
+ pid_t pid;
+
+ /* If SIGCHLD is ignored do_wait won't populate the status. */
+ kernel_sigaction(SIGCHLD, SIG_DFL);
+ pid = user_mode_thread(kunit_uapi_user_mode_thread_init, &ctx, SIGCHLD);
+ if (pid < 0) {
+ kernel_sigaction(SIGCHLD, SIG_IGN);
+ return pid;
+ }
+
+ wait_for_completion(&ctx.setup_done);
+
+ forward_err = kunit_uapi_forward_to_printk(ctx.out);
+ if (forward_err)
+ kunit_uapi_kill_pid(ctx.tgid);
+
+ wait_err = kernel_wait(ctx.tgid, &ret);
+
+ /* Restore default kernel sig handler */
+ kernel_sigaction(SIGCHLD, SIG_IGN);
+
+ if (ctx.exec_err)
+ return ctx.exec_err;
+ if (forward_err)
+ return forward_err;
+ if (wait_err < 0)
+ return wait_err;
+ return ret;
+}
+
+void kunit_uapi_run_executable(struct kunit *test, const struct blob *blob)
+{
+ const char *exe_name = kbasename(blob->path);
+ struct vfsmount *mnt;
+ int err;
+
+ mnt = kunit_uapi_mount_tmpfs();
+ KUNIT_EXPECT_FALSE_MSG(test, IS_ERR(mnt), "Could not mount tmpfs for test: %pe", mnt);
+ if (IS_ERR(mnt))
+ return;
+
+ err = kunit_uapi_write_file(mnt, exe_name, 0700, blob->data, blob_size(blob));
+ KUNIT_EXPECT_EQ_MSG(test, 0, err, "Could not add test executable: %pe", ERR_PTR(err));
+
+ if (!err) {
+ err = kunit_uapi_run_executable_in_mount(test, exe_name, mnt);
+ KUNIT_EXPECT_GE_MSG(test, err, 0, "Error when running executable: %pe\n",
+ ERR_PTR(err));
+ KUNIT_EXPECT_EQ_MSG(test, 0, err, "Executable signal/exitcode: %d/%d\n",
+ err & 0xff, err >> 8);
+ }
+
+ kern_unmount(mnt);
+
+}
+EXPORT_SYMBOL_GPL(kunit_uapi_run_executable);
Enable running UAPI tests as part of kunit. The selftests are embedded into the kernel image and their output is forwarded to kunit for unified reporting. The implementation reuses parts of usermode drivers and usermode helpers. However these frameworks are not used directly as they make it impossible to retrieve a thread's exit code. Signed-off-by: Thomas Weißschuh <thomas.weissschuh@linutronix.de> --- include/kunit/uapi.h | 17 ++++ lib/kunit/Kconfig | 9 ++ lib/kunit/Makefile | 2 + lib/kunit/uapi.c | 239 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 267 insertions(+)