Pro*_*rty 19 c linux kernel-module linux-kernel
我有一个应用程序,它有两个外部内核模块和一个用户空间守护程序.我想在启动时从C编写的守护程序代码加载模块,并在干净退出时卸载它们.我可以用比system("modprobe module");使用相应的方式更清洁的方式加载它们rmmod吗?
Cir*_*四事件 19
最小的可运行示例
使用这个简单的参数打印机模块在QEMU + Buildroot VM和Ubuntu 16.04主机上进行了测试.
我们使用init_module/ finit_module和remove_module Linux系统调用.
Linux内核为模块插入提供了两个系统调用:
init_modulefinit_module和:
man init_module
Run Code Online (Sandbox Code Playgroud)
文件:
finit_module()系统调用类似于init_module(),但是从文件描述符fd读取要加载的模块.当内核模块的真实性可以从它在文件系统中的位置确定时,它是有用的; 在可能的情况下,可以避免使用加密签名的模块来确定模块的真实性的开销.param_values参数与init_module()相同.
finit是更新的,只在v3.8中添加.更多理由:https://lwn.net/Articles/519010/
glibc似乎没有为它们提供C包装器,所以我们只创建自己的包装器syscall.
#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;
}
Run Code Online (Sandbox Code Playgroud)
#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;
}
Run Code Online (Sandbox Code Playgroud)
Busybox源解释
Busybox提供insmod_main,因为它是为极简主义而设计的,我们可以尝试从那里推断它是如何完成的.
在版本1.24.2上,入口点是IF_FEATURE_2_4_MODULES功能modutils.c.
它bb_init_module是对较旧的Linux内核2.4模块的可选支持,因此我们暂时可以忽略它.
那只是转发bb_init_module功能mmap.
try_to_mmap_module 尝试两件事:
image_size文件到内存通过.ko.
这总是设置malloc为xmalloc_open_zipped_read_close文件的大小作为副作用.
如果失败,try_to_mmap_module 将文件存入内存image.
如果文件是zip文件,则此函数可选择首先解压缩文件,否则只对其进行malloc.
我不明白为什么这个拉链业务已经完成,因为我们甚至不能依赖它,因为""它似乎并没有解压缩.
终于来了电话:
init_module(image, image_size, options);
Run Code Online (Sandbox Code Playgroud)
insmod file.elf放在内存中的可执行文件在哪里,init_module如果我们调用ulibc没有进一步的参数,则选项就是这样.
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
Run Code Online (Sandbox Code Playgroud)
man init_module是一个嵌入式libc实现,它似乎提供syscall.
如果它不存在,我认为glibc是假定的,但正如init_module所说:
glibc不支持init_module()系统调用.glibc头文件中没有提供声明,但是,通过历史的怪癖,glibc会为此系统调用导出ABI.因此,为了使用这个系统调用,在代码中手动声明接口就足够了; 或者,您可以使用syscall(2)调用系统调用.
BusyBox明智地遵循finit_moduleglibc提供的建议和用法,并提供用于系统调用的C API.
insmod/rmmod使用这些函数init_module并delete_module执行此操作,这也有一个man-page可用.它们都声明函数extern而不是包含标题,但是man-page表示它们应该在<linux/module.h>.
我建议不要使用system()任何以root权限运行的守护程序代码,因为从安全角度来看它相对容易被利用.modprobe并且rmmod,的确都是这个职位的合适的工具.但是,使用显式fork()+ exec()来调用它们会更清晰,更安全.