我有一个具有两个外部内核模块和一个用户空间守护程序的应用程序。 我想从启动时用C编写的守护程序代码加载模块,并在干净的出口卸载它们。 我可以用比system("modprobe module");
更清洁的方式加载它们system("modprobe module");
并使用相应的rmmod
卸载它们?
最小的可运行示例
使用这个简单的参数打印机模块在QEMU + Buildroot VM和Ubuntu 16.04主机上进行测试。
我们使用init_module
和remove_module
Linux系统调用 。
glibc似乎没有为它们提供一个C封装器,所以我们只是用syscall
创建自己的封装器。
insmod
:
#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(mod, len, opts) syscall(__NR_init_module, mod, len, opts) int main(int argc, char **argv) { const char *params; int fd; size_t image_size; struct stat st; void *image; if (argc < 2) { puts("Usage ./prog mymodule.ko [args]"); return EXIT_FAILURE; } if (argc < 3) { params = ""; } else { params = argv[2]; } fd = open(argv[1], O_RDONLY); 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
:
#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来源解释
Busybox提供了insmod
,因为它是为极简主义设计的,所以我们可以试着从中推断出它是如何完成的。
在版本1.24.2上,入口点位于modutils/insmod.c
函数insmod_main
。
IF_FEATURE_2_4_MODULES
是对较早的Linux内核2.4模块的可选支持,所以我们现在可以忽略它。
这只是转发到modutils.c
函数bb_init_module
。
bb_init_module
尝试两件事情:
通过try_to_mmap_module
将该文件try_to_mmap_module
到内存。
这总是将image_size
设置为.ko
文件的大小作为副作用。
如果失败,请使用xmalloc_open_zipped_read_close
将文件malloc
分配给内存。
这个函数可以选择先解压缩文件,如果是压缩文件的话,只需要mallocs。
我不明白为什么这个压缩业务完成,因为我们甚至不能依靠它,因为try_to_mmap_module
似乎try_to_mmap_module
压缩的东西。
终于来了:
init_module(image, image_size, options);
其中image
是被放入内存的可执行文件,如果我们调用insmod file.elf
而没有进一步的参数,options就是""
。
init_module
由上面提供:
#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
是一个嵌入式的libc实现,它似乎提供了init_module
。
如果它不存在,我认为glibc是假定的,但是像man init_module
那样说:
glibc不支持init_module()系统调用。 在glibc头文件中没有提供声明,但是通过一个历史的怪癖,glibc确实为这个系统调用导出了一个ABI。 因此,为了使用这个系统调用,在代码中手动声明接口就足够了。 或者,您可以使用syscall(2)调用系统调用。
BusyBox明智地遵循该建议并使用glibc提供的系统调用,并提供用于系统调用的C API。
insmod / rmmod使用函数init_module
和delete_module
来做到这一点,它也有一个可用的手册页。 它们都声明函数为extern
而不是包含头文件,但是man-page表示它们应该在<linux/module.h>
。
我建议不要在任何使用root权限运行的守护程序代码中使用system()
,因为从安全性的角度来看,它相对容易被利用。 modprobe
和rmmod
确实是这个工作的正确工具。 但是,使用一个显式的fork()
+ exec()
来调用它们会更简洁一些。
我不确定有没有比system
更清洁的方式。
但是可以肯定的是,如果你想从你的用户空间守护进程加载/卸载模块,那么你强制自己以root身份运行守护进程*,这可能不被认为是安全的。
*:或者您可以在sudoers文件中添加显式命令,但这将是部署应用程序时所要管理的噩梦。
你可以执行modprobe
和Co.所做的相同的任务,但是我怀疑这个任务可能被描述为更清洁 。