How to load Linux kernel modules from C code?
init_module
/ remove_module
minimal runnable example
Tested on a QEMU + Buildroot VM and Ubuntu 16.04 host with this simple parameter printer module .
We use the init_module
/ finit_module
and remove_module
Linux system calls.
The Linux kernel offers two system calls for module insertion:
init_module
finit_module
and:
man init_module
documents that:
The finit_module() system call is like init_module(), but reads the module to be loaded from the file descriptor fd. It is useful when the authenticity of a kernel module can be determined from its location in the filesystem; in cases where that is possible, the overhead of using cryptographically signed modules to determine the authenticity of a module can be avoided. The param_values argument is as for init_module().
finit
is newer and was added only in v3.8. More rationale: https://lwn.net/Articles/519010/
glibc does not seem to provide a C wrapper for them, so we just create our own with syscall
.
insmod.c
#define _GNU_SOURCE#include <fcntl.h>#include <stdio.h>#include <sys/stat.h>#include <sys/syscall.h>#include <sys/types.h>#include <unistd.h>#include <stdlib.h>#define init_module(module_image, len, param_values) syscall(__NR_init_module, module_image, len, param_values)#define finit_module(fd, param_values, flags) syscall(__NR_finit_module, fd, param_values, flags)int main(int argc, char **argv) { const char *params; int fd, use_finit; size_t image_size; struct stat st; void *image; /* CLI handling. */ if (argc < 2) { puts("Usage ./prog mymodule.ko [args="" [use_finit=0]"); return EXIT_FAILURE; } if (argc < 3) { params = ""; } else { params = argv[2]; } if (argc < 4) { use_finit = 0; } else { use_finit = (argv[3][0] != '0'); } /* Action. */ fd = open(argv[1], O_RDONLY); if (use_finit) { puts("finit"); if (finit_module(fd, params, 0) != 0) { perror("finit_module"); return EXIT_FAILURE; } close(fd); } else { puts("init"); fstat(fd, &st); image_size = st.st_size; image = malloc(image_size); read(fd, image, image_size); close(fd); if (init_module(image, image_size, params) != 0) { perror("init_module"); return EXIT_FAILURE; } free(image); } return EXIT_SUCCESS;}
rmmod.c
#define _GNU_SOURCE#include <fcntl.h>#include <stdio.h>#include <sys/stat.h>#include <sys/syscall.h>#include <sys/types.h>#include <unistd.h>#include <stdlib.h>#define delete_module(name, flags) syscall(__NR_delete_module, name, flags)int main(int argc, char **argv) { if (argc != 2) { puts("Usage ./prog mymodule"); return EXIT_FAILURE; } if (delete_module(argv[1], O_NONBLOCK) != 0) { perror("delete_module"); return EXIT_FAILURE; } return EXIT_SUCCESS;}
Busybox source interpretation
Busybox provides insmod
, and since it is designed for minimalism, we can try to deduce how it is done from there.
On version 1.24.2, the entry point is at modutils/insmod.c
function insmod_main
.
The IF_FEATURE_2_4_MODULES
is optional support for older Linux kernel 2.4 modules, so we can just ignore it for now.
That just forwards to modutils.c
function bb_init_module
.
bb_init_module
attempts two things:
mmap
the file to memory throughtry_to_mmap_module
.This always sets
image_size
to the size of the.ko
file as a side effect.if that fails,
malloc
the file to memory withxmalloc_open_zipped_read_close
.This function optionally unzips the file first if it is a zip, and just mallocs it otherwise.
I don't understand why this zipping business is done, since we can't even rely on it because the
try_to_mmap_module
does not seem to unzip things.
Finally comes the call:
init_module(image, image_size, options);
where image
is the executable that was put into memory, and options are just ""
if we call insmod file.elf
without further arguments.
init_module
is provided above by:
#ifdef __UCLIBC__extern int init_module(void *module, unsigned long len, const char *options);extern int delete_module(const char *module, unsigned int flags);#else# include <sys/syscall.h># define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)# define delete_module(mod, flags) syscall(__NR_delete_module, mod, flags)#endif
ulibc
is an embedded libc implementation, and it seems to provide init_module
.
If it is not present, I think glibc is assumed, but as man init_module
says:
The init_module() system call is not supported by glibc. No declaration is provided in glibc headers, but, through a quirk of history, glibc does export an ABI forthis system call. Therefore, in order to employ this system call, it is sufficient to manually declare the interface in your code; alternatively, you can invokethe system call using syscall(2).
BusyBox wisely follows that advice and uses syscall
, which glibc provides, and which offers a C API for system calls.
insmod/rmmod use the functions init_module
and delete_module
to do this, which also have a man-page available. They both declare the functions as extern
instead of including a header, but the man-page says they should be in <linux/module.h>
.
I'd recommend against the use of system()
in any daemon code that runs with root permissions as it's relatively easy to exploit from a security standpoint. modprobe
and rmmod
are, indeed, the right tools for the job. However, it'd be a bit cleaner and much more secure to use an explicit fork()
+ exec()
to invoke them.