Linux Security Modules (Part 1)

During last winter, I spent some time studying rootkits targeting the Linux kernel and I developed a tiny Linux Security Module (LSM) logging some events possibly related to rootkit presence. I might write an article or two on that subject but for now, I’d like to describe a bit how the LSM framework works. Indeed, there isn’t much documentation available except some papers from many years ago and LSM has changed a lot since that time. I delved a little into it and I think I can point out some basics that might help someone understand how the current implementation of LSM works.

Origins

LSM stands for Linux Security Modules. It is a structure integrated to the Linux kernel and whose purpose is to provide the necessary components to the implementation of Mandatory Access Control (MAC) modules, without having the need to change the kernel source code every time.

Such a framework was first mentioned in 2001 following the work of the NSA on SELinux. Linus Torvalds refused to merge this tool in the 2.5 kernel, saying that many other similar projects were under active development. Instead, he suggested to develop a modular architecture that would allow for integrating all of those different tools in a consistent way.

Crispin Cowan suggested LSM, an interface providing hooks built in the Linux kernel and allowing for installing modules reinforcing access control. This effort lasted two years and combined the work of several projects such as Immunix, SELinux, SGI and Janus, as well as individuals like Greg Kroah-Hartman and James Morris. LSM was finally merged into the 2.6 kernel in December 2003. Since then, many hooks were progressively added to extend LSM’s capacities.

Generalities about hooks

Although the name can be confusing, Linux Security Modules are not Linux kernel modules (LKM). Indeed, a kernel module is a piece of code which can be loaded into the kernel when the latter is already booted and running. It can even be compiled separately if needed. However, a LSM like SELinux is directly integrated to the kernel. Any change to the LSM source code requires a recompilation of the kernel. Then, a LSM has to be enabled in the kernel configuration before compiling it (through the traditional Kconfig system), otherwise it won’t be “active” when running the kernel. One should notice that a LSM can still be enabled at boot time through the kernel command line parameters.

Location of LSM checks and hooks during an open system call

The LSM structure integrated to the kernel provides each LSM with hooks on essential functions of the kernel, on which it can be relevant to make verifications. One of the main features of LSMs is that they are stacked. This way, traditional checks are still performed and each LSM only add extra controls and checks. An important consequence of this architecture is that a denial cannot be overridden. The above picture illustrates this behavior. If traditional checks provided by Discretionary Access Control (DAC) fail and deny the operation, the LSM hook won’t even be executed and thus won’t be able to reverse the decision.

LSM hooks are C functions. A part of the elements composing the LSM structure is defined in the include/linux/lsm_hooks.h header. We can find in it the security_hook_list structure:

/*
 * Security module hook list structure.
 * For use with generic list macros for
 * common operations.
 */
struct security_hook_list {
    struct list_head                list;
    struct list_head                *head;
    union security_list_options     hook;
};

The security_list_options union included in this structure actually contains function pointers precisely related to LSM hooks:

union security_list_options {
/* ... */
    int (*path_mkdir)(const struct path *dir, struct dentry *dentry,
                      umode_t mode);
    int (*path_rmdir)(const struct path *dir, struct dentry *dentry);
/* ... */
};

In this code snippet, we can see the hooks related to the creation and removal of directories. These various pointers are located between several #ifdef/#endif pairs which allow for only enabling hooks when the kernel is compiled with the necessary options for their correct functioning.

The hooks are added to a standard kernel linked list. This is possible thanks to the security_hook_heads structure, the security_add_hooks() and security_delete_hooks() functions as well as the LSM_HOOK_INIT() macro that we will tackle later. Another important file is security/security.c. The function that we should be interested in is the following:

/**
 * security_init - initializes the security framework
 *
 * This should be called early in the kernel initialization sequence.
 */
int __init security_init(void)
{
    pr_info("Security Framework initialized\n");

    /*
     * Load minor LSMs, with the capability module always first.
     */
    capability_add_hooks();
    yama_add_hooks();
    loadpin_add_hooks();

    /*
     * Load all the remaining security modules.
     */
    do_security_initcalls();

    return 0;
}

We notice that the basic LSM (the one whose hooks are called first) is called capability. It corresponds to the standard checks implementing DAC. This is where you should add your own <LSM-name>_add_hooks() function initializing your hooks. Keep in mind that we are here talking about minor LSMs. We will come back later to the differences between minor and major LSMs.

We also find in this file the definition of the security_hook_heads structure which contains the heads of the linked lists corresponding to each hook, thus allowing for executing them in the right order, respecting the stacking property of LSM.

Clarification about LSM hooks

First of all, most hooks provided by LSM need to return an integer value (some return void). The value 0 is equivalent to an authorization. It is also possible to return the following values in case of a denial:

  • ENOMEM: no memory available
  • EACCESS: access denied by the security policy
  • EPERM: privileges are required to do this action

In addition to denying or journalizing an operation, it is also possible to modify an object’s attributes. This could for instance be used for changing on the fly the flags passed as parameters during a memory allocation, if one estimates that the latter requires too high privileges considering its permissions, but that denying it would be too harsh.

The hooks provided by LSM can be of two different types:

  • object based hooks: these hooks are related to kernel objects. It can for instance be C structures like inodes, sockets or files. Access authorization will be based on these objects’ attributes, i.e. the different fields of the C structure (like the state field of the socket structure, which represents the state of the socket). Most frequent attributes for these objects are for instance access modes, file types or sizes, information about a filesystem, etc.
  • path based hooks: added in kernel 2.6.29, these hooks are related to paths. Consequently, they look “easier to use” but their drawback is that they don’t always uniquely identify an object (symbolic links, mount points, etc.). Though more simpler to use for a developer, they should be manipulated with caution so that they cannot be easily bypassed.

I should also mention security blobs. It is a functionality provided by LSM which allows for enabling special fields located in various kernel structures and reserved for use by security modules. Their name usually ends with the _security suffix. This allows for maintaining a context between the different hooks, clearing the way for higher-level security policies. Only major LSMs can benefit of security blobs.

When we mentioned LSM stacking, we took a look at the corresponding source code and explained that it was only for minor LSMs. Such LSMs don’t use security blobs and are called after the traditional checks and the capabilities. Minor LSMs are called before major LSMs. The latter, on the contrary, use security blobs. There can only be one major LSM enabled in the kernel at a time, and it will be called last.

Aside from these hooks and the actions they permit, the LSM framework also provides audit functionalities. Their purpose is to offer alternative ways of generating log files. Some structures and functions allowing that are defined in the include/linux/lsm_audit.h file. One can notice the common_audit_data structure which allows for specifying data which is to be audited, as well as the prototype of the common_lsm_audit() function. The latter is defined in the security/lsm_audit.c file and uses callbacks in order to write LSM-specific information. This audit functionality is quite complicated to use and adds a fair amount of code to the Linux Security Module.

Finally, the LSM framework supports the creation of pseudo-filesystems. The purpose is to be able to easily interact with the security module from userspace. This can for instance allow for loading or editing some access rules, reading audit data or modifying the module’s configuration ; all of that through simple files reads and writes, exactly like the sysfs pseudo-filesystem. However, LSM does not provide a proper level of abstraction for creating a pseudo-filesystem. One thus needs to develop it “from scratch”, using the file_system_type structure and the sysfs_create_mount_point(), register_filesystem() and kern_mount() functions. Operations related to files reads and writes to this pseudo-filesystem are registered as usual using traditional structures such as file_operations.

Current LSMs

As of May 2017, six Linux Security Modules are integrated into the Linux kernel (4.11). As pointed out earlier, they need to be explicitly enabled in the kernel configuration before compiling it. SELinux, AppArmor, Smack and TOMOYO Linux are major LSMs, whereas Yama and Loadpin are minor ones. When I started writing this article I wanted to sum up the purpose and special features of each one of them, but I have already done that in French and I’m way too lazy to translate this quite annoying part. Plus, you can find about it online rather easily.

Security of LSM

Although the LSM framework was developed with security in mind (impossibility for a kernel module to define a new LSM when the system is running, impossibility to directly use security_ops structures, etc.), it was shown that a rootkit could use it to its benefit in order to insert its own hooks, leading to privilege escalation. However, this attack was not possible on a kernel using the grsecurity patch, because the structure modified by the rootkit was read-only. Even if this attack is not possible anymore since Linux 4.2 (the problematic structures were pulled out), the concept of providing structures made to be modified so that inserting hooks into the kernel is made easier sounds relatively scary. That’s at least what the author of grsecurity thinks. Anyway, grsecurity allows for limiting these kinds of issues by setting many sensitive memory areas as read-only, making arbitrary code execution harder and reinforcing the kernel’s security as a whole and in deep. Too bad it is not free anymore, but let’s hope for KSPP to mature quickly!

I’m gonna stop here for part one and let the more technical part (or at least the one with more code snippets) for a next time.