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

  1. ozs
    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).

    [​IMG]

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

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

    Разумно для начала написать заготовку модуля безопасности. Он будет очень скромный и будет во всем соглашаться с 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, разрешающий все, что разрешает «родной» защитный механизм.

    Создаем каталог с именем модуля PtLSM: /usr/src/linux-2.6.39.4/security/ptlsm/.
    Для сборки модуля выполняем следующие действия.

    • Создаем файл Makefile:
    Код:
    obj-m := ptlsm.o
    • Создаем файл 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.
    • Редактируем /security/Makefile и /security/Kconfig — чтобы о новом модуле узнал весь мир. Добавляем строки — как у других модулей.

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

    [​IMG]

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

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

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

    [​IMG]

    Я изменил тело функции 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 <linux/usb.h>
    #include <linux/usb/hcd.h>
    Повторяем действия для вставки модуля — и покупаем крутой телефон, чтобы пользоваться компьютером.

    [​IMG]

    (с)ksajxai
    (с)habrahabr.ru
    (с)Positive Technologies
     
    1 человеку нравится это.