Как модули ядра Linux загружаются из C-кода?

У меня есть приложение, которое имеет как два внешних модуля ядра, так и демон пользователя. Я хочу загрузить модули из кода демона, написанные на C, при запуске и выгрузить их на чистый выход. Могу ли я загружать их более чистым способом, чем делать system("modprobe module"); и выгружать их с помощью соответствующего rmmod?

Ответы

Ответ 1

Минимальный пример runnable

Протестировано на 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 пытается выполнить две вещи:

  • mmap файл в память через try_to_mmap_module.

    Это всегда устанавливает image_size размер файла .ko как побочный эффект.

  • Если это не удается, malloc файл в память с помощью xmalloc_open_zipped_read_close.

    Эта функция, возможно, сначала распакует файл, если он является zip, и просто сохраняет его в противном случае.

    Я не понимаю, почему этот бизнес зацикливается, потому что мы не можем даже полагаться на него, потому что 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 говорит:

Системный вызов init_module() не поддерживается glibc. В заголовках glibc объявления не декларируются, но, благодаря причуде истории, glibc экспортирует ABI для этот системный вызов. Поэтому, чтобы использовать этот системный вызов, достаточно вручную объявить интерфейс в коде; альтернативно, вы можете ссылаться системный вызов с использованием syscall (2).

BusyBox мудро следует этому совету и использует syscall, который предоставляет glibc, и который предлагает C API для системных вызовов.

Ответ 2

insmod/rmmod использовать для этого функции init_module и delete_module, которые также имеют доступную man-страницу. Они оба объявляют функции как extern вместо включения заголовка, но на man-странице указано, что они должны быть в <linux/module.h>.

Ответ 3

Я бы рекомендовал использовать system() в любом коде демона, который работает с правами root, поскольку относительно легко использовать его с точки зрения безопасности. modprobe и rmmod являются, действительно, правильными инструментами для работы. Тем не менее, было бы немного чище и безопаснее использовать явный fork() + exec() для их вызова.

Ответ 4

Я не уверен, что есть более чистый способ, чем system.

Но наверняка, если вы хотите загрузить/выгрузить модули из своего демонстратора пользовательского пространства, вы заставите себя запустить демона как root *, который не может считаться безопасным.

*: или вы можете добавить явные команды в файл sudoers, но это будет кошмар для управления при развертывании вашего приложения.

Ответ 5

Вы можете выполнять те же задачи, что и modprobe и Co., но я сомневаюсь, что это можно охарактеризовать как более чистое.