xorl %eax, %eax

Introduction to Linux Security Modules (LSM)

with 2 comments

In this post I’ll give a brief explanation of how “Linux Security Modules” feature is implemented in the kernel. First of all this is a clever abstraction layer which allows different security modules to be safely loaded and unloaded without messing with the kernel’s code directly.
The code snippets were taken from 2.6.36 release of the Linux kernel. However, the original implementation was developed in 2001.

Hooking and Capabilities
A look in include/linux/security.h reveals a huge structure of function pointers. A snippet of that structure is shown below.

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);
        int (*capset) (struct cred *new,
                       const struct cred *old,
                       const kernel_cap_t *effective,
                       const kernel_cap_t *inheritable,
                       const kernel_cap_t *permitted);
        int (*capable) (struct task_struct *tsk, const struct cred *cred,
                        int cap, int audit);
        int (*sysctl) (struct ctl_table *table, int op);
     ...
        int (*audit_rule_match) (u32 secid, u32 field, u32 op, void *lsmrule,
                                 struct audit_context *actx);
        void (*audit_rule_free) (void *lsmrule);
#endif /* CONFIG_AUDIT */
};

These are predefined and documented callback functions that a security module can utilize to perform some security task in the specified function. The security header file then defines a series of security operations functions which by default do nothing at all in most cases. Here is an example snippet of these definitions.

/*
 * This is the default capabilities functionality.  Most of these functions
 * are just stubbed out, but a few must call the proper capable code.
 */

static inline int security_init(void)
{
        return 0;
}

static inline int security_ptrace_access_check(struct task_struct *child,
                                             unsigned int mode)
{
        return cap_ptrace_access_check(child, mode);
}

As you can see, some of the defined security operations will use the POSIX capabilities checks such as the security_ptrace_access_check() which is used in kernel/ptrace.c code like this:

int __ptrace_may_access(struct task_struct *task, unsigned int mode)
{
        const struct cred *cred = current_cred(), *tcred;
     ...
        return security_ptrace_access_check(task, mode);
}

And the capability routine is placed in security/commoncap.c (that stands for “Common Capabilities”). It’s nothing more than a simple capability check which uses RCU locking.

/**
 * cap_ptrace_access_check - Determine whether the current process may access
 *                         another
 * @child: The process to be accessed
 * @mode: The mode of attachment.
 *
 * Determine whether a process may access another, returning 0 if permission
 * granted, -ve if denied.
 */
int cap_ptrace_access_check(struct task_struct *child, unsigned int mode)
{
        int ret = 0;

        rcu_read_lock();
        if (!cap_issubset(__task_cred(child)->cap_permitted,
                          current_cred()->cap_permitted) &&
            !capable(CAP_SYS_PTRACE))
                ret = -EPERM;
        rcu_read_unlock();
        return ret;
}

LSM Framework Initialization
Now that you have a basic understanding of how LSMs such as SELinux, AppArmor, Tomoyo etc. operate in order to hook to Linux kernel routines we can move to the next topic. This means jumping to security/security.c file…
By default, Linux kernel initializes LSM like this:

/* Boot-time LSM user choice */
static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] =
        CONFIG_DEFAULT_SECURITY;

/* things that live in capability.c */
extern void __init security_fixup_ops(struct security_operations *ops);

static struct security_operations *security_ops;
static struct security_operations default_security_ops = {
        .name   = "default",
};

The default security options include just the POSIX capabilities check as we saw in the previous section. In include/linux/init.h header file we can find two initialization callback functions that are normally used to define the beginning and the end of a series of routines that the LSM needs to inialize itself.

/*
 * Used for initialization calls..
 */
typedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);
     ...
extern initcall_t __security_initcall_start[], __security_initcall_end[];

Back to security/security.c there’s the actual security module initialization function.

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

        security_fixup_ops(&default_security_ops);
        security_ops = &default_security_ops;
        do_security_initcalls();

        return 0;
}

The first call leads to a security/capability.c function that initializes the passed ‘security_ops’ structure with the available routines as you can see in the snippet here:

#define set_to_cap_if_null(ops, function)                               \
        do {                                                            \
                if (!ops->function) {                                   \
                        ops->function = cap_##function;                 \
                        pr_debug("Had to override the " #function       \
                                 " security operation with the default.\n");\
                        }                                               \
        } while (0)

void __init security_fixup_ops(struct security_operations *ops)
{
        set_to_cap_if_null(ops, ptrace_access_check);
        set_to_cap_if_null(ops, ptrace_traceme);
        set_to_cap_if_null(ops, capget);
        set_to_cap_if_null(ops, capset);
     ...
}

Then security_init() will update kernel’s ‘security_ops’ structure with the initialized one and make a call to do_security_initcalls() which basically nothing more than a loop that will call all of the functions defined in the previously mentioned include/linux/init.h callbacks.

static void __init do_security_initcalls(void)
{
        initcall_t *call;
        call = __security_initcall_start;
        while (call < __security_initcall_end) {
                (*call) ();
                call++;
        }
}

Also, in security/security.c we can find the actual hooks that are performed by default (LSMs implement their own). Here is a sample of that.

int security_ptrace_access_check(struct task_struct *child, unsigned int mode)
{
        return security_ops->ptrace_access_check(child, mode);
}

int security_ptrace_traceme(struct task_struct *parent)
{
        return security_ops->ptrace_traceme(parent);
}

Registration of an LSM
Knowing how LSM hooking and initialization work in general we can move to the next step which is how a security framework is registered. The function to this is this one:

/**
 * register_security - registers a security framework with the kernel
 * @ops: a pointer to the struct security_options that is to be registered
 *
 * 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).
 *
 * If there is already a security module registered with the kernel,
 * an error will be returned.  Otherwise %0 is returned on success.
 */
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;
}

The code is very straightforward. It will check that the given structure isn’t pointing to NULL using verify() which is shown below. Then, before setting the kernel’s ‘security_ops’ to the LSM’s one it checks that the structure passed to it is not the default one which is already being used.

static inline int __init verify(struct security_operations *ops)
{
        /* verify the security_operations structure exists */
        if (!ops)
                return -EINVAL;
        security_fixup_ops(ops);
        return 0;
}

Load LSM on Boot
The LSM framework provide the feature of setting a module to be loaded at boot time. This is done through another function of security/security.c which updates the previously discussed kernel’s values to use the selected LSM instead of the default one.

/**
 * security_module_enable - Load given security module on boot ?
 * @ops: a pointer to the struct security_operations that is to be checked.
 *
 * Each LSM must pass this method before registering its own operations
 * to avoid security registration races. This method may also be used
 * to check if your LSM is currently loaded during kernel initialization.
 *
 * Return true if:
 *      -The passed LSM is the one chosen by user at boot time,
 *      -or the passed LSM is configured as the default and the user did not
 *       choose an alternate LSM at boot time,
 *      -or there is no default LSM set and the user didn't specify a
 *       specific LSM and we're the first to ask for registration permission,
 *      -or the passed LSM is currently loaded.
 * Otherwise, return false.
 */
int __init security_module_enable(struct security_operations *ops)
{
        if (!*chosen_lsm)
                strncpy(chosen_lsm, ops->name, SECURITY_NAME_MAX);
        else if (strncmp(ops->name, chosen_lsm, SECURITY_NAME_MAX))
                return 0;

        return 1;
}

You can read the comment which is very informative and then you can see that this is nothing more than copying the chosen one to the kernel’s ‘chosen_lsm’ array shown earlier.

Resetting LSM to default
Finally, we have probably the most useful task from an exploit developer’s point of view. This is resetting the LSM framework back to its default. Inside security/security.c the routine that does exactly this, is very simple…

void reset_security_ops(void)
{
        security_ops = &default_security_ops;
}

Since there is already public exploit code that does exactly this, I’ll write about it too.

What spender does in own_the_kernel() to disable AppArmor and/or SELinux is just what the above reset_security_ops() function does.

        security_ops = (unsigned long *)get_kernel_sym("security_ops");
        default_security_ops = get_kernel_sym("default_security_ops");
        sel_read_enforce = get_kernel_sym("sel_read_enforce");
   ...
        // disable SELinux
        if (selinux_enforcing && *selinux_enforcing) {
                what_we_do = 2;
                *selinux_enforcing = 0;
        }

        if (!selinux_enabled || (selinux_enabled && *selinux_enabled == 0)) {
                // trash LSM
                if (default_security_ops && security_ops) {
                        if (*security_ops != default_security_ops)
                                what_we_do = 3;
                        *security_ops = default_security_ops;
                }
        }

He obtains the kernel symbols through either ‘/proc/kallsyms’ or ‘/proc/ksyms’ and then just changes them to the default ones. His exploit includes a feature of making the system look like it’s set with SELinux on enforcing mode but this is out of the scope of this post since it is SELinux specific.

I intentionally omitted some details such as security_initcall() macro but I’ll discuss it in more detail in future posts dealing with some popular LSMs including SELinux, Tomoyo, SMACK and AppArmor. After all, this was just an introduction.

Written by xorl

December 20, 2010 at 16:38

Posted in linux, security

2 Responses

Subscribe to comments with RSS.

  1. Hi,
    I very much appreciate your posts on the Linux kernel exploits. they are interesting and explain whats going on very well.
    I was wondering if you could do some posts on BSD OSes (FreeBSD, OpenBSD) as well.

    vroom

    December 20, 2010 at 16:52

  2. I’m writing about BSDs too. Just have a look at the “FreeBSD”, “NetBSD” and “OpenBSD” categories from the drop-down data window.
    Currently it has:
    – 22 posts on FreeBSD
    – 9 posts on NetBSD
    – 6 posts on OpenBSD
    However, if there is something that you want me to write about (it must be public) just drop me an email.

    xorl

    December 20, 2010 at 17:59


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s