如何从C代码加载Linux内核模块?

时间:2011-05-10 08:28:45

标签: c linux linux-kernel kernel-module

我有一个包含两个外部内核模块和一个用户空间守护程序的应用程序。我想在启动时从C编写的守护程序代码加载模块,并在干净退出时卸载它们。我可以用比system("modprobe module");更干净的方式加载它们并使用相应的rmmod卸载它们吗?

5 个答案:

答案 0 :(得分:19)

最小可运行示例

在QEMU + Buildroot VM和Ubuntu 16.04主机with this simple parameter printer module上测试。

我们使用init_module / finit_moduleremove_module Linux system calls

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.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;
}

GitHub upstream

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;
}

GitHub upstream

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尝试了两件事:

  • mmap通过try_to_mmap_module将文件存储到内存中。

    这总是将image_size设置为.ko文件的大小作为副作用。

  • 如果失败,请malloc将文件存入xmalloc_open_zipped_read_close内存。

    如果文件是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 <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提供的syscall,并为系统调用提供C API。

答案 1 :(得分:9)

insmod / rmmod使用函数init_moduledelete_module来执行此操作,其中还有一个可用的手册页。他们都将函数声明为extern而不是包含标题,但是man-page表示它们应该在<linux/module.h>中。

答案 2 :(得分:6)

我建议不要在任何以root权限运行的守护程序代码中使用system(),因为从安全角度来看它相对容易利用。 modprobermmod确实是这项工作的正确工具。但是,使用显式fork() + exec()来调用它们会更简洁,更安全。

答案 3 :(得分:1)

你可以执行modprobe和Co.所做的相同任务,但我怀疑它可以被描述为 cleaner

答案 4 :(得分:1)

我不确定清洁的方式是否比system

但是可以肯定的是,如果你想从你的用户空间守护进程加载/卸载模块,那么你就强迫自己以root *的身份运行守护进程,这可能不被认为是安全的。

*:或者您可以在sudoers文件中添加显式命令,但在部署应用程序时这将是一个噩梦。