Linux Security Modules (Part 2)

This is the second part of an article about Linux Security Modules. You may want to read part one first.

Integration of an LSM to the Linux kernel

First of all, one should take a look at the include/linux/lsm_hooks.h file. You can see the following lines at the end of the file:

#ifdef CONFIG_SECURITY_YAMA
extern void __init yama_add_hooks(void);
#else
static inline void __init yama_add_hooks(void) { }
#endif
#ifdef CONFIG_SECURITY_LOADPIN
void __init loadpin_add_hooks(void);
#else
static inline void loadpin_add_hooks(void) { };
#endif

This defines the <LSM-name>_add_hooks() functions if the related Kconfig options are enabled. Otherwise, those functions are defined as empty ones and the LSM hooks are not loaded.

These Kconfig options are defined in security/<LSM-name>/Kconfig. For instance, the Kconfig file for Yama looks like the following:

config SECURITY_YAMA
    bool "Yama support"
    depends on SECURITY
    default n
    help
      This selects Yama, which extends DAC support with additional
      system-wide security settings beyond regular Linux discretionary
      access controls. Currently available is ptrace scope restriction.
      Like capabilities, this security module stacks with other LSMs.
      Further information can be found in Documentation/security/Yama.txt.

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

The keyword default allows for defining the default value for the configuration option (which is of type boolean). The Yama LSM is thus disabled by default and has to be enabled before compiling the kernel. The keyword depends on makes the configuration option available only if some other kernel configuration options are enabled. There is also the select keyword which allows for automatically enabling other configuration options that are needed by the LSM. All LSMs need the “basic security options” and most of them also require for instance filesystem and network options to be able to do their work. This file is finally sourced in the “parent” file (in the directory tree) security/Kconfig.

Obviously, a Makefile is also required. For a simple minor LSM like Yama, it can look like that:

obj-$(CONFIG_SECURITY_YAMA) := yama.o

yama-y := yama_lsm.o

Just as Kconfig files, Makefiles are organized as a tree and the following lines need to be added in the security/Makefile file:

subdir-$(CONFIG_SECURITY_YAMA)          += yama

[...]

obj-$(CONFIG_SECURITY_YAMA)             += yama/

These two lines integrate the security/yama directory into the kernel compilation process (if the related Kconfig option is enabled, of course).

Documentation should be written in Documentation/security/<LSM-name>.txt.

The last file that needs to be edited to integrate an LSM to the Linux kernel is security/security.c, that we have already mentioned in the first article. In the body of the function security_init(), the function <LSM-name>_add_hooks() is called. We have also already mentioned this function and we will see its definition later. The call is located after the calls to the equivalent functions for other minor LSMs.

The remaining of the important source code is located in the security module source code itself (for instance, in security/yama/yama_lsm.c). Such files need to include the include/lsm_hooks.h header containing the prototypes of all LSM hooks, as well as the definitions of several needed structures and macros. This file also contains the definition of the security_add_hooks() function. This function is called from the <LSM-name>_add_hooks() functions. For Yama:

void __init yama_add_hooks(void)
{
    pr_info("Yama: becoming mindful.\n");
    security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks), "yama");
    yama_init_sysctl();
}

The arguments passed to this function are the <LSM-name>_hooks array and its size, obtained with the ARRAY_SIZE() macro. This array is an array of security_hook_list structures (which was described in the first part of this article) and is defined in the same file, i.e. security/yama/yama_lsm.c for Yama:

static struct security_hook_list yama_hooks[] __lsm_ro_after_init = {
    LSM_HOOK_INIT(ptrace_access_check, yama_ptrace_access_check),
    LSM_HOOK_INIT(ptrace_traceme, yama_ptrace_traceme),
    LSM_HOOK_INIT(task_prctl, yama_task_prctl),
    LSM_HOOK_INIT(task_free, yama_task_free),
};

These security_hook_list structures are obtained with the LSM_HOOK_INIT() macro defined in include/linux/lsm_hooks.h. Understanding how this macro works will make you understand the stacking of the different LSMs and their hooks:

#define LSM_HOOK_INIT(HEAD, HOOK)           \
    { .head = &security_hook_heads.HEAD,    \
      .hook = { .HEAD = HOOK } }

We see that passing the name of an LSM hook as the first parameter initializes the head field of the structure with a linked list corresponding to this hook, via the security_hook_heads structure defined in the file security/security.c. This structure is composed of as many linked lists as there are hooks defined by the LSM framework. Those linked lists are initialized with the traditional LIST_HEAD_INIT() macro provided by the Linux kernel.

The second parameter passed to the macro corresponds to the hook that is defined by the security module, for instance in security/yama/yama_lsm.c. By convention, these functions start with a prefix corresponding to the name of the LSM, i.e. yama_ for Yama. In broad outline, the remaining of an LSM source code is only a succession of hook functions definitions and their helpers. For a major LSM implementing auditing functionalities and a pseudo-filesystem, things are of course much more complex and beyond the scope of this blog post.

This mechanism is used for every single hook defined by every single LSM integrated to the Linux kernel and enabled. The security_add_hooks() function is then expected to correctly chain all those security_hook_list structures. This way, we obtain one linked list per LSM and one linked list per hook provided by the LSM framework, depending on how we walk through those lists. In a word, it is these few structures, functions and macros which implement the stacking of LSM hooks, which is itself a consequence of the stacking of the security modules that define them. Also (brace yourselves for the incoming long sentence), the order of the hooks in each linked list being directly related to the order of the calls to the <LSM-name>_add_hooks() functions of each minor LSM in the security_init() functions of the security/security.c source file, the stacking order is respected.

At last, kernel functions that contain LSM hooks call the related functions defined in security/security.c, which in turn make use of the call_int_hook() or call_void_hook() macros. For instance, if we take one Yama hook like yama_ptrace_traceme(), which corresponds to the ptrace_traceme() kernel function defined in kernel/ptrace.c:

/**
 * ptrace_traceme  --  helper for PTRACE_TRACEME
 *
 * Performs checks and sets PT_PTRACED.
 * Should be used by all ptrace implementations for PTRACE_TRACEME.
 */
static int ptrace_traceme(void)
{
    int ret = -EPERM;

    write_lock_irq(&tasklist_lock);
    /* Are we already being traced? */
    if (!current->ptrace) {
        ret = security_ptrace_traceme(current->parent);
        /*
         * Check PF_EXITING to ensure ->real_parent has not passed
         * exit_ptrace(). Otherwise we don't report the error but
         * pretend ->real_parent untraces us right after return.
         */
        if (!ret && !(current->real_parent->flags & PF_EXITING)) {
            current->ptrace = PT_PTRACED;
            __ptrace_link(current, current->real_parent);
        }
    }
    write_unlock_irq(&tasklist_lock);

    return ret;
}

We see the call to security_ptrace_traceme(), defined like the following in security/security.c:

int security_ptrace_traceme(struct task_struct *parent)
{
    return call_int_hook(ptrace_traceme, 0, parent);
}

These call_int_hook() and call_void_hook() macros are defined in the same file and simply iterate through the linked list corresponding to the hook, in order to call the LSM hooks defined by the security modules that are enabled on the running system:

/*
 * Hook list operation macros.
 *
 * call_void_hook:
 *  This is a hook that does not return a value.
 *
 * call_int_hook:
 *  This is a hook that returns a value.
 */

#define call_void_hook(FUNC, ...)                               \
    do {                                                        \
        struct security_hook_list *P;                           \
                                                                \
        list_for_each_entry(P, &security_hook_heads.FUNC, list) \
        P->hook.FUNC(__VA_ARGS__);                              \
    } while (0)

#define call_int_hook(FUNC, IRC, ...) ({                            \
    int RC = IRC;                                                   \
    do {                                                            \
        struct security_hook_list *P;                               \
                                                                    \
        list_for_each_entry(P, &security_hook_heads.FUNC, list) {   \
            RC = P->hook.FUNC(__VA_ARGS__);                         \
            if (RC != 0)                                            \
                break;                                              \
        }                                                           \
    } while (0);                                                    \
    RC;                                                             \
})

One should notice that in the case of a hook returning an integer value, the iteration is interrupted as soon as one hook function returns a value different from 0, thus satisfying the “cannot override a denial” rule.

And that’s pretty much all I wanted to share with you. I hope this can help someone trying to play with Linux Security Modules. Don’t hesitate to e-mail me if you have any question ;)