Написание OpenBSD Loadable Kernel Modules (LKM)
Написание OpenBSD Loadable Kernel Modules (LKM)
Данная статья 100 % копипаст с www.openbsd.ru
Является вольным переводом статьи OpenBSD Loadable Kernel Modules By Peter Werner [mailto: peter_a_werner@yahoo.com 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 — в этот тип входит все что не описано выше, при этом следует учесть, что при замене системных вызовов, возвращение в исходное состояние должно осуществляться кодом модуля.
Обычно модуль состоит из трех частей:
- Обработчик загрузки, выгрузки модуля
- Внешняя точка входа, используемая modload
- Функциональный код модуля
Модуль 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
Модуль Device Driver
Модуль драйвера устройств имеет схожий вид модулем системных вызовов. Описывается как:
struct lkm_dev {
MODTYPE lkm_type; /* тип модуля, в данном случае LM_SYSCALL */
int lkm_ver; /* версия lkm интерфейса */
char *lkm_name; /* имя модуля */
u_long lkm_offset; /* положение устройства в таблице cdevsw[] или bdevsw[] */
DEVTYPE lkm_devtype; /* тип устройства: блочное или символьное, значения LM_DT_CHAR или LM_DT_BLOCK */
union {
void *anon;
struct bdevsw *bdev;
struct cdevsw *cdev;
} lkm_dev; /* функции устройства */
union
{
struct bdevsw bdev;
struct cdevsw cdev;
} lkm_olddev; /* функции заменяемого устройства, при загрузке */
};
Инициализация происходит через макрос MOD_DEV:
MOD_DEV("ourdev", LM_DT_CHAR, -1, &cdev_ourdev); /* устанавливает имя символьного устройства с параметрами из cdev_ourdev */
Наше устройство будет поддерживать четыре вида операций: open, close, read и ioctl. Функционально хранит строку и число которое может быть установлено через ioctl, поддерживает чтение строки через read. Внутренне описывается как:
#define MAXMSGLEN 100
struct ourdev_io {
int value;
char msg[MAXMSGLEN];
};
При загрузке модуля мы инициализируем value с значением 13 и msg — «hello world!» как строку. Так же определяем два ioctl получения и установления значений. Оба вызова принимают аргумент ourdev_io.
Код устройства (chardev.c):
#include <sys/param.h>
#include <sys/fcntl.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/exec.h>
#include <sys/conf.h>
#include <sys/lkm.h>
/*
* Прототипы поддерживаемых операций - open, close, read and ioctl
*/
int ourdevopen (dev_t, int, int, struct proc *);
int ourdevclose (dev_t, int, int, struct proc *);
int ourdevread (dev_t, struct uio *, int);
int ourdevioctl (dev_t, u_long, caddr_t, int, struct proc *);
int ourdev_handler (struct lkm_table *, int);
/*
* Объявляем внутреннюю структуру устройства
*/
#define MAXMSGLEN 100
struct ourdev_io {
int value;
char msg[MAXMSGLEN];
};
static struct ourdev_io dio;
/*
* Здесь инициализируем вектор операций устройства.
*/
cdev_decl(ourdev);
static struct cdevsw cdev_ourdev = cdev_ourdev_init(1, ourdev);
/*
* Инициализируем lkm интерфейс
*/
MOD_DEV("ourdev", LM_DT_CHAR, -1, &cdev_ourdev)
/*
* Действия при открытие
*/
int
ourdevopen(dev_t dev, int oflags, int devtype, struct proc *p)
{
printf("device opened, hi!\n");
return(0);
}
/*
* Действия при закрытии
*/
int
ourdevclose(dev_t dev, int oflags, int devtype, struct proc *p)
{
printf("device closed! bye!\n");
return(0);
}
/*
* Действия при чтении
*/
int
ourdevread(dev_t dev, struct uio *uio, int ioflag)
{
int resid = MAXMSGLEN;
int error = 0;
do {
if (uio->uio_resid < resid)
resid = uio->uio_resid;
error = uiomove(dio.msg, resid, uio);
} while (resid > 0 && error == 0);
return(error);
}
/*
* Действия на ioctl вызовы
*/
int
ourdevioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p)
{
struct ourdev_io *d;
int error = 0;
switch(cmd) {
case ODREAD: /* чтение параметрова */
d = (struct ourdev_io *)data;
d->value = dio.value;
error = copyoutstr(&dio.msg, d->msg, MAXMSGLEN - 1, NULL);
break;
case ODWRITE: /* установка параметров */
if ((fflag & FWRITE) == 0)
return(EPERM);
d = (struct ourdev_io *)data;
dio.value = d->value;
bzero(&dio.msg, MAXMSGLEN);
error = copyinstr(d->msg, &dio.msg, MAXMSGLEN - 1, NULL);
break;
default:
error = ENODEV; /* операция не поддерживается */
break;
}
return(error);
}
/*
* внешняя точка входя для загрузки/выгрузки и т.д.
*/
int
ourdev(struct lkm_table *lkmtp, int cmd, int ver)
{
DISPATCH(lkmtp, cmd, ver, ourdev_handler, lkm_nofunc, lkm_nofunc)
}
/*
* Обработчик загрузки/выгрузки
*/
int
ourdev_handler(struct lkm_table *lkmtp, int cmd)
{
struct lkm_dev *args = lkmtp->private.lkm_dev;
if (cmd == LKM_E_LOAD) {
dio.value = 13;
strncpy(dio.msg,"hello world!\n", MAXMSGLEN - 1);
printf("loading module %s\n", args->lkm_name);
}
return 0;
}
Модуль Execution Interpreter
Модуль Miscellaneous
Данный тип модулей используется для категорий которые не прдставлены выше.
Описывается как:
struct lkm_misc {
MODTYPE lkm_type; /* тип модуля, в данном случае LM_MISC */
int lkm_ver; /* версия lkm интерфейса */
char *lkm_name; /* имя модуля */
u_long lkm_offset;
};
Структура инициализируется через макрос MOD_MISC:
MOD_MISC("ourmodule");
Следует заметить, что при данном типе модулей используется только имя модуля.
Рассмотрим все вышеописанное на примере rucd
Код модуля (rucd.c):
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/exec.h>
#include <sys/systm.h>
#include <sys/lkm.h>
/* таблица перекодировки utf в koi8r */
static u_int16_t utf2koi8r[256] = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
0x2500, 0x2502, 0x250c, 0x2510, 0x2514, 0x2518, 0x251c, 0x2524,
0x252c, 0x2534, 0x253c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590,
0x2591, 0x2592, 0x2593, 0x2320, 0x25a0, 0x2219, 0x221a, 0x2248,
0x2264, 0x2265, 0x00a0, 0x2321, 0x00b0, 0x00b2, 0x00b7, 0x00f7,
0x2550, 0x2551, 0x2552, 0x0451, 0x2553, 0x2554, 0x2555, 0x2556,
0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x255c, 0x255d, 0x255e,
0x255f, 0x2560, 0x2561, 0x0401, 0x2562, 0x2563, 0x2564, 0x2565,
0x2566, 0x2567, 0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x00a9,
0x044e, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433,
0x0445, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e,
0x043f, 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432,
0x044c, 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a,
0x042e, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413,
0x0425, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e,
0x041f, 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412,
0x042c, 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a
};
extern u_char (*cd9660_wchar2char)(u_int32_t wchar);
extern int lkmexists(struct lkm_table *);
int rucd_lkmentry(struct lkm_table *, int, int);
/*
* Инициализируем lkm интерфейс
*/
MOD_MISC("rucd")
/* Функция перекодирования с utf в koi8r */
static u_char
conv_utf2koi8r(u_int32_t wchar)
{
u_char schar;
int i;
schar = '?';
for (i = 0; i < 256; i++) {
if (utf2koi8r[i] == wchar) {
schar = i;
break;
}
}
return (schar);
}
/*
* внешняя точка входя для загрузки/выгрузки и т.д.
*/
static int
rucd_handle(struct lkm_table *lkmtp, int cmd)
{
switch (cmd) {
case LKM_E_LOAD:
if (lkmexists(lkmtp))
return (EEXIST);
cd9660_wchar2char = conv_utf2koi8r;
break;
case LKM_E_UNLOAD:
cd9660_wchar2char = NULL;
break;
default:
return (EINVAL);
}
return (0);
}
/*
* Обработчик загрузки/выгрузки
*/
int
rucd_lkmentry(struct lkm_table *lkmtp, int cmd, int ver)
{
DISPATCH(lkmtp, cmd, ver, rucd_handle, rucd_handle, lkm_nofunc);
}