Пишем модуль безопасности Linux

18.05.2012

image

Статья про реализацию модели безопасности от Дмитрия Садовникова, исследовательский центр Positive Research

Linux Security Modules (LSM) — фреймворк, добавляющий в Linux поддержку различных моделей безопасности. LSM является частью ядра начиная с Linux версии 2.6. На данный момент в официальном ядре «обитают» модули безопасности SELinux, AppArmor, Tomoyo и Smack.

Работают модули параллельно с «родной» моделью безопасности Linux — избирательным управлением доступом (Discretionary Access Control, DAC). Проверки LSM вызываются на действия, разрешенные DAC.

Применять механизм LSM можно по-разному. В большинстве случаев это добавление мандатного управления доступом (как, например, в случае с SELinux). Кроме того, можно придумать собственную модель безопасности, реализовать ее в виде модуля и легко внедрить, используя фреймворк. Рассмотрим для примера реализацию модуля, который будет давать права на действия в системе при наличии особого USB-устройства.

Поглядим на схему и попытаемся разобраться, как работает хук LSM (на примере системного вызова open).

https://www.securitylab.ru/_Article_Images/2009/09/lin-1.png

Ничего сложного. Главная задача LSM — предоставить модулям безопасности механизм для контроля доступа к объектам ядра (хуки вставлены в ядерный код прямо перед обращениями к объектам). Перед обращением ядра к внутреннему объекту будет обязательно вызвана функция проверки, предоставленная LSM.

Другим словами, LSM дает модулям возможность ответить на вопрос: «Может ли субъект S произвести действие OP над внутренним объектом ядра OBJ?»

Это — очень здорово. Это не sys_call_table хукать по самые указатели.

Разумно для начала написать заготовку модуля безопасности. Он будет очень скромный и будет во всем соглашаться с DAC. Необходимые нам исходники лежат в каталоге security среди исходников ядра.

Копаем исходники

Идем в include/linux/security.h (у меня исходники ядра версии 2.6.39.4). Самое важное здесь – могучая структура security_ops.

Вот её фрагмент:

struct security_operations

{

char name[SECURITY_NAME_MAX + 1];

int (*ptrace_access_check) (struct task_struct *child, unsigned int

mode);

int (*ptrace_traceme) (struct task_struct *parent);

int (*capget) (struct task_struct *target,

kernel_cap_t *effective,

kernel_cap_t *inheritable, kernel_cap_t *permitted);

};

Это список заранее определенных и документированных callback-функций, которые доступны модулю безопасности для выполнения проверок. По умолчанию эти функции в большинстве своем возвращают 0, тем самым разрешая любые действия. Но некоторые используют модуль безопасности POSIX. Это функции Common Capabilities, с ними можно ознакомиться в файле security/commoncap.c.

Нам в данном случае важна следующая функция из include/linux/security.c:

/**

* register_security – регистрирует модуль безопасности в ядре.

* @ops: указатель на структуру security_options, которая будет

* использоваться.

*

* This function allows a security module to register itself with the

* kernel security subsystem. Some rudimentary checking is done on

the @ops

* value passed to this function. You’ll need to check first if your

LSM

* is allowed to register its @ops by calling security_module_enable

(@ops).

*

* Если в ядре уже зарегистрирован модуль безопасности, то вернёт

ошибку. При

* успехе вернёт 0.

*/

int __init register_security(struct security_operations *ops)

{

if (verify(ops))

{

printk(KERN_DEBUG «%s could not verify »

«security_operations structure.\n», __func__);

return -EINVAL;

}

if (security_ops != &default_security_ops)

return -EAGAIN;

security_ops = ops;

return 0;

}

Пишем заготовку

У меня под рукой дистрибутив BackTrack 5 R1 (версия ядра 2.6.39.4). Взглянем на готовый модуль безопасности, например на SELinux (каталог /security/selinux/). Основной его механизм описан в файле hooks.c. На основе этого файла я создал скелет нового модуля безопасности (чуть позже мы сделаем из него что-нибудь интересное).

Монструозную структуру security_ops заполняем указателями на свои функции. Для этого достаточно у всех функции заменить selinux на название своего модуля (PtLSM в моем примере). Редактируем тела всех функций: возвращающие void делаем пустыми, int должны возвращать 0. В результате получился ничего не делающий LSM, разрешающий все, что разрешает «родной» защитный механизм. (Исходный код модуля: pastebin.com/Cst0VVQh).

Небольшое печальное отступление. Начиная с версии 2.6.24, из соображений безопасности ядро перестало экспортировать символы необходимые для написания модулей безопасности в виде загружаемых модулей ядра (Linux Kernel Module, LKM). Например, исчезла из экспорта функция register_security, которая позволяет зарегистрировать модуль и его хуки. Поэтому будем собирать ядро с нашим модулем.

Создаем каталог с именем модуля PtLSM: /usr/src/linux-2.6.39.4/security/ptlsm/.

Для сборки модуля выполняем следующие действия.

1. Создаем файл Makefile:

obj-m := ptlsm.o

2. Создаем файл Kconfig:

config SECURITY_PTLSM

bool «Positive Protection»

default n

help

This module does nothing in a positive kind of way.

If you are unsure how to answer this question, answer N.

3. Редактируем /security/Makefile и /security/Kconfig — чтобы о новом модуле узнал весь мир. Добавляем строки — как у других модулей.

Мои файлы с добавленным PtLSM:

1) Makefile — pastebin.com/k7amsnQK

2) Kconfig — pastebin.com/YDsPBGAz

Далее, в каталоге с исходниками ядра делаем make menuconfig, выбираем PtLSM в пункте «Security Options».

https://www.securitylab.ru/_Article_Images/2009/09/lin-2.png

Теперь make, make modules_install, make install. Модуль помещен в ядре, и при помощи утилиты dmesg можно посмотреть, что именно он пишет в лог.

Пишем суперкрутой модуль

Пришло время сделать наш модуль суперкрутым! Пусть модуль запрещает делать что-либо на компьютере, если к нему не подключено USB-устройство с заданными Vendor ID и Product ID (в моем примере это будут ID телефона Galaxy S II).

https://www.securitylab.ru/_Article_Images/2009/09/lin-3.png

Я изменил тело функции ptlsm_inode_create, которая проверяет, имеет ли тот или иной процесс возможность создавать файлы. Если функция нашла «устройство высшей власти», то даст разрешение на выполнение. Аналогичные проверки можно производить с любыми другими действиями.

static int ptlsm_inode_create(struct inode *dir, struct dentry

*dentry, int mask)

{

if (find_usb_device() != 0)

{

printk(KERN_ALERT «You shall not pass!\n»);

return -EACCES;

}

else {

printk(KERN_ALERT «Found supreme USB device\n»);

}

return 0;

} Теперь неплохо было бы написать функцию find_usb_device. Она будет анализировать все USB-устройства в системе и отыскивать то, которое имеет нужный ID. Данные о USB-устройствах хранятся в виде деревьев, корни которых называются root hub device. Список всех корней лежит в списке шин usb_bus_list.

static int find_usb_device(void)

{

struct list_head* buslist;

struct usb_bus* bus;

int retval = -ENODEV;

mutex_lock(&usb_bus_list_lock);

for (buslist = usb_bus_list.next; buslist != &usb_bus_list;

buslist = buslist->next)

{

bus = container_of(buslist, struct usb_bus, bus_list);

retval = match_device(bus->root_hub);

if (retval == 0)

{

break;

}

}

mutex_unlock(&usb_bus_list_lock);

return retval;

}

И, наконец, функция match_device, проверяющая Vendor ID и Product ID.

static int match_device(struct usb_device* dev)

{

int retval = -ENODEV;

int child;

if ((dev->descriptor.idVendor == vendor_id) &&

(dev->descriptor.idProduct == product_id))

{

return 0;

}

for (child = 0; child < dev->maxchild; ++child)

{

if (dev->children[child])

{

retval = match_device(dev->children[child]);

if (retval == 0)

{

return retval;

}

}

}

return retval;

} Для работы с USB подключим пару заголовков.

#include

#include

Повторяем действия для вставки модуля — и покупаем крутой телефон, чтобы пользоваться компьютером.

https://www.securitylab.ru/_Article_Images/2009/09/lin-4.png

Подробнее: https://www.securitylab.ru/analytics/424663.php