Writing a Linux Security Module

    Linux Security Modules (LSM) is a framework that adds support for various security models in Linux. LSM has been part of the kernel since Linux version 2.6. Currently, SELinux, AppArmor, Tomoyo, and Smack security modules "reside" in the official core.

    The modules work in parallel with the "native" Linux security model - Discretionary Access Control (DAC). LSM checks are called for actions authorized by the DAC.

    There are many ways to apply the LSM mechanism. In most cases, this is the addition of mandatory access control (as, for example, with SELinux). In addition, you can come up with your own security model, implement it in the form of a module, and easily implement it using the framework. For example, consider the implementation of a module that will give rights to actions in the system if there is a special USB device.

    Let's take a look at the diagram and try to figure out how the LSM hook works (using the open system call as an example).



    Nothing complicated. The main task of LSM is to provide security modules with a mechanism for controlling access to kernel objects (hooks are inserted into the kernel code right before accessing the objects). Before the kernel accesses the internal object, the validation function provided by LSM will be invoked.

    In other words, LSM gives modules the ability to answer the question: “Can subject S perform an OP action on an internal OBJ kernel object?”
    This is very cool. This is not a sys_call_table to hook on most pointers.

    It is reasonable to start by writing a draft security module. He will be very modest and will agree with the DAC in everything. The sources we need are in the security directory among the kernel sources.

    Dig the source

    We go to include / linux / security.h (I have the source code for the kernel version 2.6.39.4). The most important thing here is the powerful security_ops structure.
    Here is a fragment of it:

    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);
    	…
    };
    


    This is a list of predefined and documented callback functions that are available to the security module for performing checks. By default, these functions for the most part return 0, thereby allowing any action. But some use the POSIX security module. These are Common Capabilities functions; they can be found in the file security / commoncap.c.

    In this case, the following function from include / linux / security.c is important to us:

     /**
     * 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;
    }
    


    Writing a blank

    I have at hand the distribution kit BackTrack 5 R1 (kernel version 2.6.39.4). Let's take a look at a ready-made security module, for example, on SELinux (directory / security / selinux /). Its main mechanism is described in the hooks.c file. Based on this file, I created the skeleton of a new security module (a little later we will make something interesting out of it).

    We fill the monstrous security_ops structure with pointers to our functions. To do this, it is enough for all functions to replace selinux with the name of their module (PtLSM in my example). We edit the bodies of all functions: we return void empty, int should return 0. The result is an LSM that does nothing, allowing everything that the native defense mechanism allows. (Source code of the module: pastebin.com/Cst0VVQh ).

    A slight sad retreat. Starting with version 2.6.24, for security reasons, the kernel stopped exporting the characters necessary for writing security modules as loadable kernel modules (Linux Kernel Module, LKM). For example, the register_security function, which allows registering a module and its hooks, has disappeared from export. Therefore, we will assemble the kernel with our module.

    Create a directory with the name of the PtLSM module: /usr/src/linux-2.6.39.4/security/ptlsm/.
    To build the module, perform the following steps.

    1. Create the file Makefile:

    obj-m: = ptlsm.o

    2. Create the file 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. Edit / security / Makefile and / security / Kconfig - so that the whole world knows about the new module. Add lines - like other modules.

    My files with added PtLSM:
    1) Makefile - pastebin.com/k7amsnQK
    2) Kconfig - pastebin.com/YDsPBGAz

    Next, in the directory with the kernel sources, make make menuconfig, select PtLSM in the "Security Options" item.



    Now make, make modules_install, make install. The module is placed in the kernel, and using the dmesg utility you can see what it writes to the log.

    Writing a super cool module

    It's time to make our module super cool! Let the module prohibit doing anything on the computer if a USB device with the specified Vendor ID and Product ID is not connected to it (in my example, these will be the Galaxy S II phone ID).



    I changed the body of the ptlsm_inode_create function, which checks if a process has the ability to create files. If the function found a “device of higher authority”, then it will give permission to execute. Similar checks can be performed with any other actions.

    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;
    }
    


    Now it would be nice to write the find_usb_device function. She will analyze all the USB devices in the system and look for the one that has the desired ID. Data on USB devices is stored in the form of trees whose roots are called root hub device. A list of all the roots lies in the usb_bus_list 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;
    }
    


    And finally, the match_device function, which checks the Vendor ID and 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;
    }
    


    To work with USB, connect a couple of headers.

    #include 
    #include 


    We repeat the steps to insert the module - and buy a cool phone to use the computer.


    Also popular now: