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

"); 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.

");\ } \ } 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.

", __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.