initial commit

This commit is contained in:
mappu 2024-07-13 10:56:47 +12:00
commit 7bd293ec13
8 changed files with 256 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*.o
.*.cmd
*.ko
Module.symvers
eprog.mod
eprog.mod.c
eprog_user_blob.S
sample

12
Makefile Normal file
View File

@ -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

37
README.md Normal file
View File

@ -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

39
build-module.sh Executable file
View File

@ -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 <<EOF
.section .init.rodata, "a"
.global embedded_umh_start
embedded_umh_start:
.incbin "$(realpath "$embedbin")"
.global embedded_umh_end
embedded_umh_end:
EOF
# Build the kernel module
make clean
make
# Finished
test -f eprog.ko
echo "Build complete."
}
main "$@"

6
build-sample.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
set -eu
# Build sample binary
musl-gcc -Os -s -static -o sample sample.c

141
eprog_kern.c Normal file
View File

@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-2.0-only
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/freezer.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/slab.h>
#include <linux/umh.h>
#include <linux/usermode_driver.h>
#include <linux/version.h>
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");

1
modules.order Normal file
View File

@ -0,0 +1 @@
/mkkmod/eprog.o

12
sample.c Normal file
View File

@ -0,0 +1,12 @@
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv) {
for (int i = 0; i < 60; ++i) {
puts("hello world");
sleep(1);
}
exit(0);
}