From 7bd293ec134092e2c053a784858828775929f7ca Mon Sep 17 00:00:00 2001 From: mappu Date: Sat, 13 Jul 2024 10:56:47 +1200 Subject: [PATCH] initial commit --- .gitignore | 8 +++ Makefile | 12 +++++ README.md | 37 +++++++++++++ build-module.sh | 39 ++++++++++++++ build-sample.sh | 6 +++ eprog_kern.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++++ modules.order | 1 + sample.c | 12 +++++ 8 files changed, 256 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100755 build-module.sh create mode 100755 build-sample.sh create mode 100644 eprog_kern.c create mode 100644 modules.order create mode 100644 sample.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e091aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.o +.*.cmd +*.ko +Module.symvers +eprog.mod +eprog.mod.c +eprog_user_blob.S +sample diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b525ebe --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +TARGET := eprog +KERN_VER ?= $(shell uname -r) +KERN_DIR := /lib/modules/$(KERN_VER)/build/ + +obj-m += $(TARGET).o +$(TARGET)-objs += eprog_kern.o eprog_user_blob.o + +all: + make -C $(KERN_DIR) M=$(PWD) modules + +clean: + make -C $(KERN_DIR) M=$(PWD) clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..10c2276 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# mkkmod + +Pack a binary into a self-contained, self-executing Linux kernel module using the usermode driver infrastructure. + +Adapted from [an example by Richard Weinberger](https://sigma-star.at/blog/2023/07/embedded-go-prog/). + +License: GPL-2.0-only + +## Usage + +- Build sample binary to embed: `./build-sample.sh` +- Build kernel module: `./build-module.sh ./sample` + - To build for the non-running kernel: `KERN_VER=6.9.8-200.fc40.x86_64 ./build-module.sh ./sample` +- Run: `insmod ./eprog.ko` + +## Requirements + +- The generated module must have a GPL-compatible license to use `EXPORT_SYMBOL_GPL` functions. +- Requires kernel 5.9 or later. +- Kernel version needs to export the umd functionality. + +If you get errors of the form `modpost: "fork_usermode_driver" [...] undefined!` check if your kernel exposes the functions in `/usr/src/linux-headers-$(uname -r)/Module.symvers` and/or that the functions are present in `/proc/kallsyms`. + +Compatibility tests: + +|Distro|Kernel version|Notes +|---|---|--- +|Debian 12|6.1.0-22|❌ Missing symbols +|Fedora 40|6.9.8-200|✅ Features are present + +## Reference + +- https://sigma-star.at/blog/2023/07/embedded-go-prog/ +- https://git.kernel.org/pub/scm/linux/kernel/git/rw/misc.git/tree/drivers/misc/embedded_prog?h=embedded_go_prog +- https://medium.com/@adityapatnaik27/linux-kernel-module-in-tree-vs-out-of-tree-build-77596fc35891 +- https://github.com/torvalds/linux/blob/master/include/linux/usermode_driver.h +- https://github.com/torvalds/linux/blob/master/kernel/usermode_driver.c diff --git a/build-module.sh b/build-module.sh new file mode 100755 index 0000000..b8cc552 --- /dev/null +++ b/build-module.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# +# Usage: ./build-module.sh path-to-my-embed-binary + +set -eu + +main() { + + local embedbin="$1" + + if [[ ! -f $embedbin ]] ; then + echo "Can't find '$embedbin' to embed" >&2 + exit 1 + fi + + # Generate asm include file + cat > eprog_user_blob.S < +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern char embedded_umh_start; +extern char embedded_umh_end; + +static struct umd_info eprog_ctx = { + .driver_name = "eprog_user", +}; + +static struct task_struct *eprog_thread_tsk; +static char *read_buf; +#define READ_BUFSZ 128 + +static int eprog_thread(void *data) +{ + struct umd_info *eprog_ctx = data; + loff_t pos = 0; + ssize_t nread; + + set_freezable(); + + for (;;) { + if (kthread_should_stop()) + break; + + if (try_to_freeze()) + continue; + + nread = kernel_read(eprog_ctx->pipe_from_umh, read_buf, READ_BUFSZ - 1, &pos); + if (nread > 0) { + read_buf[nread] = '\0'; + pr_info("From userspace: %s", read_buf); + } else if (nread == -ERESTARTSYS) { + break; + } else { + pr_err("Fatal error while reading from userspace: %ld\n", nread); + /* + * Suspend ourself and wait for termination. + */ + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } + } + + return 0; +} + +// thread_group_exited: deleted in >= 6.9 kernels +// @ref https://lkml.org/lkml/2024/2/5/1125 +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,9,0) +static bool thread_group_exited(struct pid *pid) +{ + struct task_struct *task; + bool exited; + + rcu_read_lock(); + task = pid_task(pid, PIDTYPE_PID); + exited = !task || + (READ_ONCE(task->exit_state) && thread_group_empty(task)); + rcu_read_unlock(); + + return exited; +} +#endif + +static void kill_umh(struct umd_info *eprog_ctx) +{ + struct pid *eprog_tgid = eprog_ctx->tgid; + + kill_pid(eprog_tgid, SIGKILL, 1); + + wait_event(eprog_tgid->wait_pidfd, thread_group_exited(eprog_tgid)); + + umd_cleanup_helper(eprog_ctx); +} + +static int __init eprog_init(void) +{ + int ret; + + read_buf = kmalloc(READ_BUFSZ, GFP_KERNEL); + if (!read_buf) { + ret = -ENOMEM; + goto out; + } + + ret = umd_load_blob(&eprog_ctx, &embedded_umh_start, &embedded_umh_end - &embedded_umh_start); + if (ret) { + pr_err("Unable to load embedded user mode helper blob: %i\n", ret); + kfree(read_buf); + goto out; + } + + ret = fork_usermode_driver(&eprog_ctx); + if (ret) { + pr_err("Unable to start embedded user mode helper: %i\n", ret); + umd_unload_blob(&eprog_ctx); + kfree(read_buf); + goto out; + } + + eprog_thread_tsk = kthread_create(eprog_thread, &eprog_ctx, "eprog_thread"); + if (IS_ERR(eprog_thread_tsk)) { + ret = PTR_ERR(eprog_thread_tsk); + pr_err("Unable to start kernel thread: %i\n", ret); + kill_umh(&eprog_ctx); + umd_unload_blob(&eprog_ctx); + kfree(read_buf); + goto out; + } + + wake_up_process(eprog_thread_tsk); + ret = 0; +out: + return ret; +} + +static void __exit eprog_exit(void) +{ + kthread_stop(eprog_thread_tsk); + kill_umh(&eprog_ctx); + kfree(read_buf); + umd_unload_blob(&eprog_ctx); +} + +module_init(eprog_init); +module_exit(eprog_exit); +MODULE_LICENSE("GPL"); diff --git a/modules.order b/modules.order new file mode 100644 index 0000000..95fb246 --- /dev/null +++ b/modules.order @@ -0,0 +1 @@ +/mkkmod/eprog.o diff --git a/sample.c b/sample.c new file mode 100644 index 0000000..1f86e4e --- /dev/null +++ b/sample.c @@ -0,0 +1,12 @@ +#include +#include +#include + +int main(int argc, char** argv) { + for (int i = 0; i < 60; ++i) { + puts("hello world"); + sleep(1); + } + + exit(0); +}