如何从C代码加载Linux内核模块?
我有一个具有两个外部内核模块和一个用户空间守护程序的应用程序。 我想在启动时从C编写的守护程序代码加载模块,并在干净退出时卸载它们。 我可以用比system("modprobe module");
更清洁的方式加载它们system("modprobe module");
并使用相应的rmmod
卸载它们?
最小的可运行示例
使用这个简单的参数打印机模块在QEMU + Buildroot VM和Ubuntu 16.04主机上进行了测试。
我们使用init_module
/ finit_module
和remove_module
Linux系统调用 。
Linux内核为模块插入提供了两个系统调用:
-
init_module
-
finit_module
和:
man init_module
文件:
finit_module()系统调用类似于init_module(),但是从文件描述符fd读取要加载的模块。 当内核模块的真实性可以从它在文件系统中的位置确定时,它是有用的。 在可能的情况下,可以避免使用加密签名的模块来确定模块的真实性的开销。 param_values参数与init_module()相同。
finit更新,仅在v3.8中添加。 更多理由: https : //lwn.net/Articles/519010/
glibc似乎没有为它们提供C包装器,所以我们只用syscall
创建自己的包装器。
insmod
:
#define _GNU_SOURCE #include #include #include #include #include #include #include #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
:
#define _GNU_SOURCE #include #include #include #include #include #include #include #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模块的可选支持,因此我们IF_FEATURE_2_4_MODULES
可以忽略它。
那只是转发到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
到内存。如果它是一个zip,这个函数可以选择首先解压缩文件,否则只是mallocs。
我不明白为什么这个压缩业务已经完成,因为我们甚至不能依赖它,因为
try_to_mmap_module
似乎没有解压缩。
终于来了电话:
init_module(image, image_size, options);
其中image
是放入内存的可执行文件,如果我们调用insmod file.elf
而没有进一步的参数,则选项只是""
。
上面提供了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 # 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提供的syscall
,并为系统调用提供C API。
insmod / rmmod使用函数init_module
和delete_module
来执行此操作,它还具有可用的手册页。 它们都将函数声明为extern
而不是包含标题,但是man-page表示它们应该在
。
我建议不要在任何以root权限运行的守护进程代码中使用system()
,因为从安全角度来看它相对容易被利用。 事实上, modprobe
和rmmod
是这项工作的正确工具。 但是,使用显式fork()
+ exec()
来调用它们会更简洁,更安全。
你可以执行modprobe
和Co.所做的相同任务,但我怀疑它可以被描述为更清洁 。
我不确定它是否比system
更清洁 。
但可以肯定的是,如果要从用户空间守护程序加载/卸载模块,则强制自己以root *身份运行守护程序,这可能不被视为安全。
*:或者您可以在sudoers文件中添加显式命令,但这将是部署应用程序时管理的噩梦。