diff --git a/Documentation/dev-tools/kunit/api/index.rst b/Documentation/dev-tools/kunit/api/index.rst index 5cdb552a0808f..34d8fee9a9705 100644 --- a/Documentation/dev-tools/kunit/api/index.rst +++ b/Documentation/dev-tools/kunit/api/index.rst @@ -9,6 +9,7 @@ API Reference test resource functionredirection + uapi clk of platformdevice @@ -32,6 +33,10 @@ Documentation/dev-tools/kunit/api/functionredirection.rst - Documents the KUnit Function Redirection API +Documentation/dev-tools/kunit/api/uapi.rst + + - Documents the KUnit Userspace testing API + Driver KUnit API ================ diff --git a/Documentation/dev-tools/kunit/api/uapi.rst b/Documentation/dev-tools/kunit/api/uapi.rst new file mode 100644 index 0000000000000..1f01b5c6c9db4 --- /dev/null +++ b/Documentation/dev-tools/kunit/api/uapi.rst @@ -0,0 +1,14 @@ +.. SPDX-License-Identifier: GPL-2.0 + +================== +Userspace Test API +================== + +This file documents all of the userspace testing API. +Userspace tests are built as :ref:`kbuild userprogs `, +linked statically and without any external dependencies. + +For the widest platform compatibility they should use nolibc, as provided by `init/Makefile.nolibc`. + +.. kernel-doc:: include/kunit/uapi.h + :internal: diff --git a/Documentation/kbuild/makefiles.rst b/Documentation/kbuild/makefiles.rst index 24a4708d26e8e..6e54ad4cfecbf 100644 --- a/Documentation/kbuild/makefiles.rst +++ b/Documentation/kbuild/makefiles.rst @@ -891,6 +891,8 @@ This is possible in two ways: This will tell kbuild to build lxdialog even if not referenced in any rule. +.. _kbuild_userprogs: + Userspace Program support ========================= diff --git a/MAINTAINERS b/MAINTAINERS index 77fdfcb55f060..6135b8b416094 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14321,6 +14321,15 @@ S: Maintained F: Documentation/devicetree/bindings/leds/backlight/kinetic,ktz8866.yaml F: drivers/video/backlight/ktz8866.c +KUNIT UAPI TESTING FRAMEWORK (in addition to KERNEL UNIT TESTING FRAMEWORK) +M: Thomas Weißschuh +S: Maintained +F: include/kunit/uapi.h +F: lib/kunit/kunit-example-uapi.c +F: lib/kunit/kunit-test-uapi.c +F: lib/kunit/kunit-uapi.c +F: lib/kunit/uapi-preinit.c + KVM PARAVIRT (KVM/paravirt) M: Paolo Bonzini R: Vitaly Kuznetsov @@ -18811,6 +18820,8 @@ M: Willy Tarreau M: Thomas Weißschuh S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/nolibc/linux-nolibc.git +F: init/Kconfig.nolibc +F: init/Makefile.nolibc F: tools/include/nolibc/ F: tools/testing/selftests/nolibc/ diff --git a/fs/coredump.c b/fs/coredump.c index 29df8aa19e2e7..965ad150b9d5f 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -1012,7 +1012,7 @@ static bool coredump_pipe(struct core_name *cn, struct coredump_params *cprm, helper_argv[argi] = cn->corename + argv[argi]; helper_argv[argi] = NULL; - sub_info = call_usermodehelper_setup(helper_argv[0], helper_argv, NULL, + sub_info = call_usermodehelper_setup(AT_FDCWD, helper_argv[0], helper_argv, NULL, GFP_KERNEL, umh_coredump_setup, NULL, cprm); if (!sub_info) diff --git a/fs/exec.c b/fs/exec.c index 9ea3a775d51e9..264c53fdd7e9a 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1846,7 +1846,7 @@ static int do_execveat_common(int fd, struct filename *filename, return bprm_execve(bprm); } -int kernel_execve(const char *kernel_filename, +int kernel_execve(int dirfd, const char *kernel_filename, const char *const *argv, const char *const *envp) { int retval; @@ -1856,7 +1856,7 @@ int kernel_execve(const char *kernel_filename, return -EINVAL; CLASS(filename_kernel, filename)(kernel_filename); - CLASS(bprm, bprm)(AT_FDCWD, filename, 0); + CLASS(bprm, bprm)(dirfd, filename, 0); if (IS_ERR(bprm)) return PTR_ERR(bprm); diff --git a/include/kunit/uapi.h b/include/kunit/uapi.h new file mode 100644 index 0000000000000..1e05853551241 --- /dev/null +++ b/include/kunit/uapi.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * KUnit Userspace testing API. + * + * Copyright (C) 2026, Linutronix GmbH. + * Author: Thomas Weißschuh + */ + +#ifndef _KUNIT_UAPI_H +#define _KUNIT_UAPI_H + +#include + +struct kunit; + +/** + * struct kunit_uapi_blob - Blob embedded build artifact + * @path: Path of the embedded artifact. + * @data: Start of the embedded data in memory. + * @end: End of the embedded data in memory. + */ +struct kunit_uapi_blob { + const char *const path; + const u8 *data; + const u8 *end; +}; + +#if IS_ENABLED(CONFIG_KUNIT_UAPI) + +/** + * KUNIT_UAPI_EMBED_BLOB() - Embed another build artifact into the kernel + * @_name: The name of symbol under which the artifact is embedded. + * @_path: Path to the artifact on disk. + * + * Embeds a build artifact like a userspace executable into the kernel or current module. + * The build artifact is read from disk and needs to be already built. + */ +#define KUNIT_UAPI_EMBED_BLOB(_name, _path) \ + asm ( \ + " .pushsection .rodata, \"a\" \n" \ + " .global " __stringify(CONCATENATE(_name, _data)) " \n" \ + __stringify(CONCATENATE(_name, _data)) ": \n" \ + " .incbin " __stringify(_path) " \n" \ + " .size " __stringify(CONCATENATE(_name, _data)) ", " \ + ". - " __stringify(CONCATENATE(_name, _data)) " \n" \ + " .global " __stringify(CONCATENATE(_name, _end)) " \n" \ + __stringify(CONCATENATE(_name, _end)) ": \n" \ + " .popsection \n" \ + ); \ + \ + extern const char CONCATENATE(_name, _data)[]; \ + extern const char CONCATENATE(_name, _end)[]; \ + \ + static const struct kunit_uapi_blob _name = { \ + .path = _path, \ + .data = CONCATENATE(_name, _data), \ + .end = CONCATENATE(_name, _end), \ + } \ + +#else /* !CONFIG_KUNIT_UAPI */ + +/* Unresolved external reference, to be optimized away */ +#define KUNIT_UAPI_EMBED_BLOB(_name, _path) \ + extern const struct kunit_uapi_blob _name + +#endif /* CONFIG_KUNIT_UAPI */ + +/** + * kunit_uapi_run_kselftest() - Run a userspace kselftest as part of kunit + * @test: The test context object. + * @executable: kselftest executable to run + * + * Runs the kselftest and forwards its TAP output and exit status to kunit. + */ +void kunit_uapi_run_kselftest(struct kunit *test, const struct kunit_uapi_blob *executable); + +#endif /* _KUNIT_UAPI_H */ diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h index 65abd5ab8836c..04e2b7a85b2b0 100644 --- a/include/linux/binfmts.h +++ b/include/linux/binfmts.h @@ -144,7 +144,7 @@ int copy_string_kernel(const char *arg, struct linux_binprm *bprm); extern void set_binfmt(struct linux_binfmt *new); extern ssize_t read_code(struct file *, unsigned long, loff_t, size_t); -int kernel_execve(const char *filename, +int kernel_execve(int dirfd, const char *filename, const char *const *argv, const char *const *envp); #endif /* _LINUX_BINFMTS_H */ diff --git a/include/linux/mount.h b/include/linux/mount.h index acfe7ef86a1b3..d8689ce61a423 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -10,6 +10,7 @@ #ifndef _LINUX_MOUNT_H #define _LINUX_MOUNT_H +#include #include #include @@ -99,6 +100,7 @@ extern bool our_mnt(struct vfsmount *mnt); extern struct vfsmount *kern_mount(struct file_system_type *); extern void kern_unmount(struct vfsmount *mnt); +DEFINE_FREE(kern_unmount, struct vfsmount *, if (_T) kern_unmount(_T)); extern int may_umount_tree(struct vfsmount *); extern int may_umount(struct vfsmount *); int do_mount(const char *, const char __user *, diff --git a/include/linux/umh.h b/include/linux/umh.h index daa6a7048c11e..6670b9ff85d4b 100644 --- a/include/linux/umh.h +++ b/include/linux/umh.h @@ -20,6 +20,7 @@ struct file; struct subprocess_info { struct work_struct work; struct completion *complete; + int dirfd; const char *path; char **argv; char **envp; @@ -34,7 +35,7 @@ extern int call_usermodehelper(const char *path, char **argv, char **envp, int wait); extern struct subprocess_info * -call_usermodehelper_setup(const char *path, char **argv, char **envp, +call_usermodehelper_setup(int dirfd, const char *path, char **argv, char **envp, gfp_t gfp_mask, int (*init)(struct subprocess_info *info, struct cred *new), void (*cleanup)(struct subprocess_info *), void *data); diff --git a/init/Kconfig b/init/Kconfig index 444ce811ea674..f51a8e4a6e5dc 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -91,6 +91,8 @@ config CC_CAN_LINK default $(cc_can_link_user,$(m64-flag)) if 64BIT default $(cc_can_link_user,$(m32-flag)) +source "init/Kconfig.nolibc" + # Fixed in GCC 14, 13.3, 12.4 and 11.5 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113921 config GCC_ASM_GOTO_OUTPUT_BROKEN diff --git a/init/Kconfig.nolibc b/init/Kconfig.nolibc new file mode 100644 index 0000000000000..07488ef18f4fd --- /dev/null +++ b/init/Kconfig.nolibc @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 + +config ARCH_HAS_NOLIBC + bool + default y if ARM + default y if ARM64 + default y if LOONGARCH + default y if M68K + default y if MIPS + default y if PPC + default y if RISCV + default y if S390 + default y if SPARC + default y if SUPERH + default y if UML_X86 + default y if X86 diff --git a/init/Makefile.nolibc b/init/Makefile.nolibc new file mode 100644 index 0000000000000..dacc78ab4c81c --- /dev/null +++ b/init/Makefile.nolibc @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# Compiler flags, which are necessary to build userspace applications with the +# in-kernel libc "nolibc". + +ifeq ($(and $(CONFIG_ARCH_HAS_NOLIBC),$(CONFIG_HEADERS_INSTALL)),y) + +NOLIBC_USERCFLAGS := -nostdlib -nostdinc -static -ffreestanding \ + -fno-asynchronous-unwind-tables -fno-stack-protector \ + -I$(objtree)/usr/include -I$(srctree)/tools/include/nolibc/ + +NOLIBC_USERLDFLAGS := -nostdlib -nostdinc -static + +endif # CONFIG_ARCH_HAS_NOLIBC && CONFIG_HEADERS_INSTALL diff --git a/init/main.c b/init/main.c index 1cb395dd94e43..598ec8c59e9b4 100644 --- a/init/main.c +++ b/init/main.c @@ -1500,7 +1500,7 @@ static int run_init_process(const char *init_filename) pr_debug(" with environment:\n"); for (p = envp_init; *p; p++) pr_debug(" %s\n", *p); - return kernel_execve(init_filename, argv_init, envp_init); + return kernel_execve(AT_FDCWD, init_filename, argv_init, envp_init); } static int try_to_run_init_process(const char *init_filename) diff --git a/kernel/module/kmod.c b/kernel/module/kmod.c index a25dccdf7aa75..a85c57a707af5 100644 --- a/kernel/module/kmod.c +++ b/kernel/module/kmod.c @@ -95,7 +95,7 @@ static int call_modprobe(char *orig_module_name, int wait) argv[3] = module_name; /* check free_modprobe_argv() */ argv[4] = NULL; - info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL, + info = call_usermodehelper_setup(AT_FDCWD, modprobe_path, argv, envp, GFP_KERNEL, NULL, free_modprobe_argv, NULL); if (!info) goto free_module_name; diff --git a/kernel/umh.c b/kernel/umh.c index cffda97d961cb..5cdcba6005d92 100644 --- a/kernel/umh.c +++ b/kernel/umh.c @@ -106,7 +106,7 @@ static int call_usermodehelper_exec_async(void *data) commit_creds(new); wait_for_initramfs(); - retval = kernel_execve(sub_info->path, + retval = kernel_execve(sub_info->dirfd, sub_info->path, (const char *const *)sub_info->argv, (const char *const *)sub_info->envp); out: @@ -331,6 +331,7 @@ static void helper_unlock(void) /** * call_usermodehelper_setup - prepare to call a usermode helper + * @dirfd: directory to resolve path against * @path: path to usermode executable * @argv: arg vector for process * @envp: environment for process @@ -352,7 +353,7 @@ static void helper_unlock(void) * Function must be runnable in either a process context or the * context in which call_usermodehelper_exec is called. */ -struct subprocess_info *call_usermodehelper_setup(const char *path, char **argv, +struct subprocess_info *call_usermodehelper_setup(int dirfd, const char *path, char **argv, char **envp, gfp_t gfp_mask, int (*init)(struct subprocess_info *info, struct cred *new), void (*cleanup)(struct subprocess_info *info), @@ -366,8 +367,10 @@ struct subprocess_info *call_usermodehelper_setup(const char *path, char **argv, INIT_WORK(&sub_info->work, call_usermodehelper_exec_work); #ifdef CONFIG_STATIC_USERMODEHELPER + sub_info->dirfd = AT_FDCWD; sub_info->path = CONFIG_STATIC_USERMODEHELPER_PATH; #else + sub_info->dirfd = dirfd; sub_info->path = path; #endif sub_info->argv = argv; @@ -484,7 +487,7 @@ int call_usermodehelper(const char *path, char **argv, char **envp, int wait) struct subprocess_info *info; gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL; - info = call_usermodehelper_setup(path, argv, envp, gfp_mask, + info = call_usermodehelper_setup(AT_FDCWD, path, argv, envp, gfp_mask, NULL, NULL, NULL); if (info == NULL) return -ENOMEM; diff --git a/lib/kobject_uevent.c b/lib/kobject_uevent.c index 871941c9830cf..c179dc2f06b3d 100644 --- a/lib/kobject_uevent.c +++ b/lib/kobject_uevent.c @@ -625,7 +625,7 @@ int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, goto exit; retval = -ENOMEM; - info = call_usermodehelper_setup(env->argv[0], env->argv, + info = call_usermodehelper_setup(AT_FDCWD, env->argv[0], env->argv, env->envp, GFP_KERNEL, NULL, cleanup_uevent_env, env); if (info) { diff --git a/lib/kunit/Kconfig b/lib/kunit/Kconfig index 498cc51e493dc..f3dc9fac811a2 100644 --- a/lib/kunit/Kconfig +++ b/lib/kunit/Kconfig @@ -141,4 +141,20 @@ config KUNIT_UML_PCI If unsure, say N. +config KUNIT_UAPI + tristate "KUnit UAPI testing framework" + depends on KUNIT + depends on ARCH_HAS_NOLIBC + depends on !STATIC_USERMODEHELPER + depends on !LTO_CLANG # https://github.com/llvm/llvm-project/issues/112920 + select HEADERS_INSTALL + select DEVTMPFS + default KUNIT + help + Enables support for building and running userspace selftests as part of kunit. + These tests should be statically linked and use kselftest.h or kselftest_harness.h + for status reporting. + + In most cases this should be left as its default. + endif # KUNIT diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile index 656f1fa35abcc..1a4af6b66524b 100644 --- a/lib/kunit/Makefile +++ b/lib/kunit/Makefile @@ -1,3 +1,5 @@ +include $(srctree)/init/Makefile.nolibc + obj-$(CONFIG_KUNIT) += kunit.o kunit-objs += test.o \ @@ -12,6 +14,13 @@ kunit-objs += test.o \ device.o \ platform.o +userprogs += uapi-preinit +uapi-preinit-userccflags += -static $(NOLIBC_USERCFLAGS) +obj-$(CONFIG_KUNIT_UAPI) += kunit-uapi.o + +CFLAGS_kunit-uapi.o := -Wa,-I$(obj) +$(obj)/kunit-uapi.o: $(obj)/uapi-preinit + ifeq ($(CONFIG_KUNIT_DEBUGFS),y) kunit-objs += debugfs.o endif @@ -20,6 +29,15 @@ endif obj-$(if $(CONFIG_KUNIT),y) += hooks.o obj-$(CONFIG_KUNIT_TEST) += kunit-test.o + +userprogs += kunit-test-uapi +kunit-test-uapi-userccflags := -static $(NOLIBC_USERCFLAGS) + +ifdef CONFIG_KUNIT_UAPI +CFLAGS_kunit-test.o := -Wa,-I$(obj) +$(obj)/kunit-test.o: $(obj)/kunit-test-uapi +endif + obj-$(CONFIG_KUNIT_TEST) += platform-test.o # string-stream-test compiles built-in only. @@ -29,3 +47,11 @@ obj-$(CONFIG_KUNIT_TEST) += assert_test.o endif obj-$(CONFIG_KUNIT_EXAMPLE_TEST) += kunit-example-test.o + +userprogs += kunit-example-uapi +kunit-example-uapi-userccflags := -static $(NOLIBC_USERCFLAGS) + +ifdef CONFIG_KUNIT_UAPI +CFLAGS_kunit-example-test.o := -Wa,-I$(obj) +$(obj)/kunit-example-test.o: $(obj)/kunit-example-uapi +endif diff --git a/lib/kunit/kunit-example-test.c b/lib/kunit/kunit-example-test.c index 0bae7b7ca0b05..febabc995405f 100644 --- a/lib/kunit/kunit-example-test.c +++ b/lib/kunit/kunit-example-test.c @@ -8,6 +8,7 @@ #include #include +#include /* * This is the most fundamental element of KUnit, the test case. A test case @@ -489,6 +490,19 @@ static void example_params_test_with_init_dynamic_arr(struct kunit *test) KUNIT_EXPECT_EQ(test, param_val - param_val, 0); } +/* + * This test shows the usage of UAPI tests. + */ +static void example_uapi_test(struct kunit *test) +{ + KUNIT_UAPI_EMBED_BLOB(kunit_example_uapi, "kunit-example-uapi"); + + if (IS_ENABLED(CONFIG_KUNIT_UAPI)) + kunit_uapi_run_kselftest(test, &kunit_example_uapi); + else + kunit_skip(test, "CONFIG_KUNIT_UAPI is not enabled"); +} + /* * Here we make a list of all the test cases we want to add to the test suite * below. @@ -514,6 +528,7 @@ static struct kunit_case example_test_cases[] = { kunit_array_gen_params, example_param_init_dynamic_arr, example_param_exit_dynamic_arr), KUNIT_CASE_SLOW(example_slow_test), + KUNIT_CASE(example_uapi_test), {} }; diff --git a/lib/kunit/kunit-example-uapi.c b/lib/kunit/kunit-example-uapi.c new file mode 100644 index 0000000000000..5875b0d680d44 --- /dev/null +++ b/lib/kunit/kunit-example-uapi.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit Userspace example test. + * + * Copyright (C) 2026, Linutronix GmbH. + * Author: Thomas Weißschuh + * + * This is *userspace* code. + */ + +#include "../../tools/testing/selftests/kselftest.h" + +int main(void) +{ + ksft_print_header(); + ksft_set_plan(4); + ksft_test_result_pass("userspace test 1\n"); + ksft_test_result_pass("userspace test 2\n"); + ksft_test_result_skip("userspace test 3: some reason\n"); + ksft_test_result_pass("userspace test 4\n"); + ksft_finished(); +} diff --git a/lib/kunit/kunit-test-uapi.c b/lib/kunit/kunit-test-uapi.c new file mode 100644 index 0000000000000..2e75e57865e33 --- /dev/null +++ b/lib/kunit/kunit-test-uapi.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit Userspace selftest. + * + * Copyright (C) 2026, Linutronix GmbH. + * Author: Thomas Weißschuh + * + * This is *userspace* code. + */ + +#include +#include +#include + +#include "../../tools/testing/selftests/kselftest.h" + +static void test_procfs(void) +{ + char buf[256]; + ssize_t r; + int fd; + + fd = open("/proc/self/comm", O_RDONLY); + if (fd == -1) { + ksft_test_result_fail("procfs: open() failed: %s\n", strerror(errno)); + return; + } + + r = read(fd, buf, sizeof(buf)); + close(fd); + + if (r == -1) { + ksft_test_result_fail("procfs: read() failed: %s\n", strerror(errno)); + return; + } + + if (r != 16 || strncmp("kunit-test-uapi\n", buf, 16) != 0) { + ksft_test_result_fail("procfs: incorrect comm\n"); + return; + } + + ksft_test_result_pass("procfs\n"); +} + +int main(void) +{ + ksft_print_header(); + ksft_set_plan(1); + test_procfs(); + ksft_finished(); +} diff --git a/lib/kunit/kunit-test.c b/lib/kunit/kunit-test.c index 126e30879dad6..b62e24fb9c55d 100644 --- a/lib/kunit/kunit-test.c +++ b/lib/kunit/kunit-test.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -914,10 +915,31 @@ static struct kunit_suite kunit_stub_test_suite = { .test_cases = kunit_stub_test_cases, }; +static void kunit_uapi_test(struct kunit *test) +{ + KUNIT_UAPI_EMBED_BLOB(kunit_test_uapi, "kunit-test-uapi"); + + if (IS_ENABLED(CONFIG_KUNIT_UAPI)) + kunit_uapi_run_kselftest(test, &kunit_test_uapi); + else + kunit_skip(test, "CONFIG_KUNIT_UAPI is not enabled"); +} + +static struct kunit_case kunit_uapi_test_cases[] = { + KUNIT_CASE(kunit_uapi_test), + {} +}; + +static struct kunit_suite kunit_uapi_test_suite = { + .name = "kunit_uapi", + .test_cases = kunit_uapi_test_cases, +}; + kunit_test_suites(&kunit_try_catch_test_suite, &kunit_resource_test_suite, &kunit_log_test_suite, &kunit_status_test_suite, &kunit_current_test_suite, &kunit_device_test_suite, - &kunit_fault_test_suite, &kunit_stub_test_suite); + &kunit_fault_test_suite, &kunit_stub_test_suite, + &kunit_uapi_test_suite); MODULE_DESCRIPTION("KUnit test for core test infrastructure"); MODULE_LICENSE("GPL v2"); diff --git a/lib/kunit/kunit-uapi.c b/lib/kunit/kunit-uapi.c new file mode 100644 index 0000000000000..702d26878ccd1 --- /dev/null +++ b/lib/kunit/kunit-uapi.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit Userspace testing API. + * + * Copyright (C) 2026, Linutronix GmbH. + * Author: Thomas Weißschuh + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define KUNIT_LOG_DEVICE "kunit-log" + +enum { + KSFT_PASS = 0, + KSFT_FAIL = 1, + KSFT_XFAIL = 2, + KSFT_XPASS = 3, + KSFT_SKIP = 4, +}; + +KUNIT_UAPI_EMBED_BLOB(kunit_uapi_preinit, "uapi-preinit"); + +static struct vfsmount *kunit_uapi_mount_fs(const char *name) +{ + struct file_system_type *type; + + type = get_fs_type(name); + if (!type) + return ERR_PTR(-ENODEV); + + return kern_mount(type); +} + +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; + } + + return 0; +} + +static const char *kunit_uapi_executable_target(const struct kunit_uapi_blob *executable) +{ + return kbasename(executable->path); +} + +static int kunit_uapi_write_executable(struct vfsmount *mnt, + const struct kunit_uapi_blob *executable) +{ + return kunit_uapi_write_file(mnt, kunit_uapi_executable_target(executable), 0755, + executable->data, executable->end - executable->data); +} + +struct kunit_uapi_usermodehelper_ctx { + struct vfsmount *mnt; + struct kunit *test; +}; + +static int kunit_uapi_get_cwd(struct vfsmount *mnt) +{ + CLASS(get_unused_fd, fd)(O_RDONLY); + if (fd < 0) + return fd; + + struct file *file __free(fput) = file_open_root_mnt(mnt, "/", O_DIRECTORY, 0); + if (IS_ERR(file)) + return PTR_ERR(file); + + fd_install(fd, no_free_ptr(file)); + + return take_fd(fd); +} + +static int kunit_uapi_open_standard_streams(void) +{ + struct vfsmount *devtmpfs __free(kern_unmount) = kunit_uapi_mount_fs("devtmpfs"); + if (IS_ERR(devtmpfs)) + return PTR_ERR(devtmpfs); + + CLASS(get_unused_fd, stdin_fd)(O_RDONLY); + if (stdin_fd < 0) + return stdin_fd; + + CLASS(get_unused_fd, stdout_fd)(O_WRONLY); + if (stdout_fd < 0) + return stdout_fd; + + CLASS(get_unused_fd, stderr_fd)(O_WRONLY); + if (stderr_fd < 0) + return stderr_fd; + + struct file *logfile __free(fput) = file_open_root_mnt(devtmpfs, KUNIT_LOG_DEVICE, + O_RDWR, 0); + if (IS_ERR(logfile)) + return PTR_ERR(logfile); + + fd_install(stdin_fd, no_free_ptr(logfile)); + fd_install(stdout_fd, fget(stdin_fd)); + fd_install(stderr_fd, fget(stdin_fd)); + + take_fd(stdin_fd); + take_fd(stdout_fd); + take_fd(stderr_fd); + + return 0; +} + +static int kunit_uapi_usermodehelper_init(struct subprocess_info *info, struct cred *new) +{ + struct kunit_uapi_usermodehelper_ctx *ctx = info->data; + int ret, dirfd; + + ret = kunit_uapi_open_standard_streams(); + if (ret) + return ret; + + dirfd = kunit_uapi_get_cwd(ctx->mnt); + if (dirfd < 0) + return dirfd; + + kernel_sigaction(SIGKILL, SIG_DFL); + kernel_sigaction(SIGABRT, SIG_DFL); + + current->kunit_test = ctx->test; + + info->dirfd = dirfd; + + return 0; +} + +static int kunit_uapi_run_executable_in_mount(struct kunit *test, + const struct kunit_uapi_blob *executable, + struct vfsmount *mnt) +{ + struct kunit_uapi_usermodehelper_ctx ctx = { + .test = test, + .mnt = mnt, + }; + struct subprocess_info *info; + const char *const argv[] = { + kunit_uapi_executable_target(executable), + NULL + }; + + info = call_usermodehelper_setup(AT_FDCWD, kunit_uapi_preinit.path, (char **)argv, NULL, + GFP_KERNEL, kunit_uapi_usermodehelper_init, NULL, &ctx); + if (!info) + return -ENOMEM; + + /* Flush delayed fput so exec can open the file read-only */ + flush_delayed_fput(); + + return call_usermodehelper_exec(info, UMH_WAIT_PROC); +} + +static int kunit_uapi_run_executable(struct kunit *test, const struct kunit_uapi_blob *executable) +{ + int err; + + struct vfsmount *mnt __free(kern_unmount) = kunit_uapi_mount_fs("ramfs"); + if (IS_ERR(mnt)) + return PTR_ERR(mnt); + + err = kunit_uapi_write_executable(mnt, executable); + if (err) + return err; + + err = kunit_uapi_write_executable(mnt, &kunit_uapi_preinit); + if (err) + return err; + + err = kunit_uapi_run_executable_in_mount(test, executable, mnt); + if (err) + return err; + + return 0; +} + +void kunit_uapi_run_kselftest(struct kunit *test, const struct kunit_uapi_blob *executable) +{ + u8 exit_code, exit_signal; + int err; + + err = kunit_uapi_run_executable(test, executable); + if (err < 0) + KUNIT_FAIL_AND_ABORT(test, "Could not run test executable: %pe\n", ERR_PTR(err)); + + exit_code = err >> 8; + exit_signal = err & 0xff; + + if (exit_signal) + KUNIT_FAIL_AND_ABORT(test, "kselftest exited with signal: %d\n", exit_signal); + else if (exit_code == KSFT_PASS) + ; /* Noop */ + else if (exit_code == KSFT_FAIL) + KUNIT_FAIL_AND_ABORT(test, "kselftest exited with code KSFT_FAIL\n"); + else if (exit_code == KSFT_XPASS) + KUNIT_FAIL_AND_ABORT(test, "kselftest exited with code KSFT_XPASS\n"); + else if (exit_code == KSFT_XFAIL) + ; /* Noop */ + else if (exit_code == KSFT_SKIP) + kunit_mark_skipped(test, "kselftest exited with code KSFT_SKIP\n"); + else + KUNIT_FAIL_AND_ABORT(test, "kselftest exited with unknown exit code: %d\n", + exit_code); +} +EXPORT_SYMBOL_GPL(kunit_uapi_run_kselftest); + +struct kunit_uapi_log_private { + struct mutex mutex; + struct seq_buf buf; + char data[4096]; +}; + +static int kunit_uapi_log_open(struct inode *ino, struct file *file) +{ + struct kunit_uapi_log_private *priv; + + priv = kmalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->mutex); + seq_buf_init(&priv->buf, priv->data, sizeof(priv->data)); + + file->private_data = priv; + + return 0; +} + +static void kunit_uapi_log_str(struct kunit *test, const char *str, size_t len) +{ + kunit_log(KERN_INFO, test, KUNIT_SUBSUBTEST_INDENT "%.*s", (int)len, str); +} + +static void kunit_uapi_print_buf_to_log(struct kunit *test, struct seq_buf *s) +{ + const char *start, *lf; + + if (s->size == 0 || s->len == 0) + return; + + start = seq_buf_str(s); + while ((lf = strchr(start, '\n'))) { + kunit_uapi_log_str(test, start, lf - start + 1); + start = ++lf; + } + + /* Remove printed data from buffer */ + memmove(s->buffer, start, start - s->buffer); + s->len -= start - s->buffer; +} + +static ssize_t kunit_uapi_log_write(struct file *file, const char __user *ubuf, size_t count, + loff_t *off) +{ + struct kunit_uapi_log_private *priv = file->private_data; + struct seq_buf *buf = &priv->buf; + struct kunit *test; + + test = kunit_get_current_test(); + if (!test) + return -ENODEV; + + guard(mutex)(&priv->mutex); + + if (seq_buf_has_overflowed(buf)) + return -E2BIG; + + if (buf->size < buf->len + count) { + seq_buf_set_overflow(buf); + kunit_warn(test, "KUnit UAPI line buffer has overflowed\n"); + return -E2BIG; + } + + if (copy_from_user(buf->buffer + buf->len, ubuf, count)) + return -EFAULT; + + buf->len += count; + + kunit_uapi_print_buf_to_log(test, &priv->buf); + + return count; +} + +static int kunit_uapi_log_release(struct inode *ino, struct file *file) +{ + struct kunit_uapi_log_private *priv = file->private_data; + struct kunit *test; + + mutex_destroy(&priv->mutex); + + test = kunit_get_current_test(); + if (!test) { + kfree(priv); + return -ENODEV; + } + + /* Flush last partial line */ + kunit_uapi_log_str(test, priv->buf.buffer, priv->buf.len); + kunit_uapi_log_str(test, "\n", 1); + + kfree(priv); + return 0; +} + +static const struct file_operations kunit_uapi_log_fops = { + .owner = THIS_MODULE, + .open = kunit_uapi_log_open, + .release = kunit_uapi_log_release, + .write = kunit_uapi_log_write, +}; + +static struct miscdevice kunit_uapi_log = { + .minor = MISC_DYNAMIC_MINOR, + .name = KUNIT_LOG_DEVICE, + .fops = &kunit_uapi_log_fops, +}; +module_misc_device(kunit_uapi_log); + +MODULE_DESCRIPTION("KUnit UAPI testing framework"); +MODULE_AUTHOR("Thomas Weißschuh + * + * This is *userspace* code. + */ + +#include +#include + +#include "../../tools/testing/selftests/kselftest.h" + +#define KUNIT_UAPI_CHDIR_FD 3 + +static int setup_api_mount(const char *target, const char *fstype) +{ + int ret; + + ret = mkdir(target, 0755); + if (ret && errno != EEXIST) + return -errno; + + ret = mount("none", target, fstype, 0, NULL); + if (ret && errno != EBUSY) + return -errno; + + return 0; +} + +static void exit_failure(const char *stage, int err) +{ + /* If preinit fails synthesize a failed test report. */ + ksft_print_header(); + ksft_set_plan(1); + ksft_test_result_fail("Failed during test setup: %s: %s\n", stage, strerror(-err)); + ksft_finished(); +} + +int main(int argc, char **argv, char **envp) +{ + int ret; + + ret = fchdir(KUNIT_UAPI_CHDIR_FD); + close(KUNIT_UAPI_CHDIR_FD); + if (ret) + exit_failure("fchdir", ret); + + ret = setup_api_mount("/proc", "proc"); + if (ret) + exit_failure("mount /proc", ret); + + ret = setup_api_mount("/sys", "sysfs"); + if (ret) + exit_failure("mount /sys", ret); + + ret = setup_api_mount("/dev", "devtmpfs"); + if (ret) + exit_failure("mount /dev", ret); + + ret = execve(argv[0], argv, envp); + if (ret) + exit_failure("execve", ret); + + return 0; +} diff --git a/security/keys/request_key.c b/security/keys/request_key.c index a7673ad86d18d..f6f3d4bc0bdab 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -101,7 +101,7 @@ static int call_usermodehelper_keys(const char *path, char **argv, char **envp, { struct subprocess_info *info; - info = call_usermodehelper_setup(path, argv, envp, GFP_KERNEL, + info = call_usermodehelper_setup(AT_FDCWD, path, argv, envp, GFP_KERNEL, umh_keys_init, umh_keys_cleanup, session_keyring); if (!info) diff --git a/tools/testing/kunit/qemu_configs/loongarch.py b/tools/testing/kunit/qemu_configs/loongarch.py index a92422967d1da..1dba755284f11 100644 --- a/tools/testing/kunit/qemu_configs/loongarch.py +++ b/tools/testing/kunit/qemu_configs/loongarch.py @@ -11,6 +11,8 @@ CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_CPU_HAS_LSX=y +CONFIG_CPU_HAS_LASX=y ''', qemu_arch='loongarch64', kernel_path='arch/loongarch/boot/vmlinux.elf',