Написание OpenBSD Loadable Kernel Modules (LKM)

Материал из OpenBSD-Wiki
Перейти к навигации Перейти к поиску

Написание OpenBSD Loadable Kernel Modules (LKM)

Данная статья 100% копипаст с www.openbsd.ru

Является вольным переводом статьи OpenBSD Loadable Kernel Modules By Peter Werner peter_a_werner@yahoo.com оригинал

Введение

LKM позволяют динамически добавлять или удалять функциональность рабочей системы. Это также позволяет разработчикам тестировать изменения частей ядра без перезагрузки системы.

Недостаток LKM - потенциальная дыра в безопасности. Для загрузки модулей securelevelядра должен быть меньше 0. Если требуется загрузить модуль, то необходимо указать это в rc.securelevel. Если вы разрабатываете модуль, то необходимо установить securelevel = -1, таким образом можно загружать и выгружать модуль в любое время.

Взаимодействие с /dev/lkm выполняется через вызовы ioctl(2). В основном используется modload(8), modunload(8) и modstat(8) для загрузки, выгрузки и получения статистики.

Интерфейс lkm определяет пять типов модулей:

  • System calls
  • Virtual File System
  • Device Driver
  • Execution Interpreter
  • Miscellaneous

System calls - заменяет системные вызовы. Все системные вызовы могут быть заменены, но стоит обратить внимания на ioctl, так как он используется для загрузки и выгрузки модуля. После выгрузки модуля замененный системный вызов возвращается на исходный. Virtual File System - добавляет виртуальные файловые системы. Device Driver - добавляет новые символьные или блочные устройства. Execution Interpreter - добавляет код для загрузки и выполнения двоичных файлов, которые не поддерживаются системой, примером могут быть эмуляции выполнения двоичных файлов различных операционных систем. Miscellaneous - в этот тип входит все что не описано выше, при этом следует учесть, что при замене системных вызовов, возвращение в исходное состояние должно осуществляться кодом модуля.

Обычно модуль состоит из трех частей:

  1. Обработчик загрузки, выгрузки модуля
  2. Внешняя точка входа, используемая modload
  3. Функциональный код модуля

Модуль System Call

Здесь мы добавим новый системный вызов который будет выводить аргументы вызова. Прототип функции:

int syscall(int, char *)

Внутреннее описание lkm структуры выглядит так:

struct lkm_syscall {
    MODTYPE    lkm_type;            /* тип модуля, в данном случае LM_SYSCALL */  
    int        lkm_ver;             /* версия lkm интерфейса */
    сhar       *lkm_name;           /* имя модуля */
    u_long     lkm_offset;          /* положение системного вызова в таблице */
    struct sysent   *lkm_sysent;    /* указатель на struct sysent системного вызова */
    struct sysent   lkm_oldent;  /* место для копии содержимого таблицы вызовов до загрузки */
};

Инициализация происходит используя макрос MOD_SYSCALL:

MOD_SYSCALL("ourcall", -1, &newcallent);    /* устанавливает имя "ourcall", положение в таблице системных вызовов,
                                             * в данном случае -1 т.е. не имеет значение, положение определяется автоматически,
                                             * newcallent - данные системного вызова
                                             */

Так же должны описать обработчик загрузки/выгрузки модуля. Обработчики устанавливают функции исполняемые при загрузке, выгрузке и получении статистики. Если обработчик не требуется просто указывается функция lkm_nofunc() для данного типа события.

Внешняя точка входа модуля ourcall использует макрос DISPATCH:

int
ourcall(lkmtp, cmd, ver)
    struct lkm_table *lkmtp;
    int              cmd;
    int              ver;
{
    DISPATCH(lkmtp, cmd, ver, ourcall_handler, ourcall_handler, lkm_nofunc);    
}

где ourcall_handler - функция обработчик, а cmd - вид команды, который может принимать значение:

  • LKM_E_LOAD - загрузка
  • LKM_E_UNLOAD - выгрузка
  • LKM_E_STAT - получение статистики

Собственно код модуля (ourcall.c):

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/cdefs.h>
#include <sys/conf.h>
#include <sys/mount.h>
#include <sys/exec.h>
#include <sys/lkm.h>
#include <sys/proc.h>
#include <sys/syscallargs.h>


/* прототип нашего системного вызова */
int newcall (struct proc *, void *, int *);      /* Все системные вызовы имеют три аргумента: Указатель на 
                                                  * struct proc - процесс который вызывает, указатель void на аргументы и 
                                                  * указатель на возвращаемое значение.
                                                  */
 /* аргументы системного вызова */
 struct newcall_args{
      syscallarg(int) value;
      syscallarg(char *) msg;
 };

 /* 
  * Определяем наш системный вызов. Первый аргумент кол-во аргументов, второй - размер аргументов, 
  * третий флаги - SY_NOLOCK или SY_MPSAFE, и четвертый - собственно функция системного вызова.
  */

 static struct sysent newcallent = {2, sizeof(struct newcall_args), 0, newcall};

 /*
  * Инициализируем внутреннюю структуру нашего модуля 
  */    

 MOD_SYSCALL("ourcall", -1, &newcallent);

 /*
  * Обработчик загрузки/выгрузки модуля
  */

 static int
 ourcall_handler(struct lkm_table *lkmtp, int cmd)
 {
      if (cmd == LKM_E_LOAD) 
      printf("hi!\n");
      else if (cmd == LKM_E_UNLOAD)
           printf("bye!\n");

      return(0);
 }

 /* 
  * Внешняя точка входа модуля, используется для загрузки, выгрузки и получения статистики через modload(8),
  * modunload(8) и modstat(8). Устанавливаем функцию обработчик ourcall_handler, вызываемую при загрузке и
  * выгрузке модуля. 
  */

 int
 ourcall(struct lkm_table *lkmtp, int cmd, int ver)
 {
      DISPATCH(lkmtp, cmd, ver, ourcall_handler, ourcall_handler, lkm_nofunc);
 }

 /* 
  * Собственно сам код системного вызова
  */

 int
 newcall(struct proc *p, void *v, int *retval)
 {
      struct newcall_args *uap = v;
      printf("newcall called with msg %s and value %d\n", SCARG(uap, msg), SCARG(uap, value));
      return(0);
 }

Компиляция и установка:

# cc -D_KERNEL -I/sys -c ourcall.c -o syscall.o
# modload -o ourcall.o -eourcall syscall.o
Module loaded as ID 0

# modtstat
Type     Id Off Loadaddr Size Info     Rev Module Name
SYSCALL   0 210 d76cd000 0001 d76cd1a0   2 ourcall

# dmesg | tail -2
hi!
DDB symbols added: 372192 bytes

Протестировать модуль можно при помощи следующей программы (testourcall.c):

#include <stdio.h>
#include <stdlib.h>

static void      usage(void);

int
main(int argc, char *argv[])
{
    int err = 0;
    int syscall_num = 0;

    if (argc != 2)
        usage();

    if ((syscall_num = atoi(argv[1]))>0)
        if (err = syscall(syscall_num, 10, "testourcall"))
            errx(err,"syscall");
}

static void  usage(void){
    extern char *__progname;
    (void)fprintf(stderr, "usage: %s value\n", __progname);
    exit(1);
}

Соберем программу:

# make testcall
cc -O2 -pipe    -o testourcall testourcall.c

Запустим и получим результат(210 - значение offset, полученное через modstat(8)):

# ./testourcall 210
# dmesg | tail -1
newcall called with msg testourcall and value 10

Чтобы выгрузить модуль:

# modunload -n ourcall

Получим результат:

# dmesg | tail -1
bye!

Модуль Virtual File System

Добавление виртуальной файловой системы очень простая задача. Необходимо лишь добавить точку входа.

Структура vfs модуля выглядит так:

struct lkm_vfs {
    MODTYPE lkm_type;               /* тип модуля, в данном случае LM_VFS */
    int     lkm_ver;                /* версия lkm интерфейса */
    char    *lkm_name;              /* имя модуля */
    u_long  lkm_offset;             /* положение vfs */
    struct vfsconf  *lkm_vfsconf;   /* указатель на vfs операции */
};

В этом случае положение (offset) не используется.

Структура инициализируется через макрос MOD_VFS:

MOD_VFS("nullfs", -1, &nullfs_vfsconf)

Первый аргумент это имя модуля, второй положение, в данном случае не имеет значения. Наконец инициализированная структура vfsconf файловой системы.

Рассмотрим все вышеописанное на примере nullfs

код модуля (null_lkm.c):

#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/mount.h>
#include <sys/exec.h>
#include <sys/lkm.h>
#include <sys/file.h>
#include <sys/errno.h>

extern struct vfsops null_vfsops;
int nullfs_lkmentry(struct lkm_table *, int, int);

/* Определяем вектор vfs операций */

#define MOUNT_NULLFS    "nullfs"
#define FS_NULLFS   22

struct vfsconf null_vfsconf = {
    &null_vfsops, MOUNT_NULLFS, FS_NULLFS, 0, 0, NULL
};

/*
 * Инициализируем внутреннюю структуру нашего модуля 
 */    
MOD_VFS("nullfs", -1, &null_vfsconf)

/* 
 * Внешняя точка входа модуля, используется для загрузки, выгрузки и получения статистики через modload(8),
 * modunload(8) и modstat(8). Устанавливаем lkm_nofunc, т.к. vfs подсистема сама занимается инициализацией и т.д.
 */

int
nullfs_lkmentry(struct lkm_table *lkmtp, int cmd, int ver)
{
    DISPATCH(lkmtp, cmd, ver, lkm_nofunc, lkm_nofunc, lkm_nofunc);
}

Компиляция и загрузка:

# cd lkm/
# make
# make load
# modstat
Type     Id Off Loadaddr Size Info     Rev Module Name
VFS       0  -1 d80b7000 0002 d80b85e4   2 nullfs

Утилита монтирования nullfs системы находится bin/mount_nullfs Код модуля доступен через cvs, адрес: http://www.openbsd.ru/cgi-bin/cvsweb/src/lkm/nullfs