Как загрузить модули ядра Linux из кода C? - PullRequest
19 голосов
/ 10 мая 2011

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

Ответы [ 5 ]

19 голосов

Пример минимального запуска

Протестировано на виртуальной машине QEMU + Buildroot и хосте 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.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-файл, и просто распределить его по другому.

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

9 голосов
/ 10 мая 2011

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

6 голосов
/ 10 мая 2011

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

1 голос
/ 10 мая 2011

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

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

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

1 голос
/ 10 мая 2011

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

...