The Ultimate Aim of Kernel Exploitation - Process Credentials

Overview

Userspace exploitation often has the end goal of code execution. In the case of kernel exploitation, we already have code execution; our aim is to escalate privileges, so that when we spawn a shell (or do anything else) using execve("/bin/sh", NULL, NULL) we are dropped as root.

To understand this, we have a talk a little about how privileges and credentials work in Linux.

The cred struct

The cred struct contains all the permissions a task holds. The ones that we care about are typically these:

struct cred {
	/* ... */
	
	kuid_t		uid;		/* real UID of the task */
	kgid_t		gid;		/* real GID of the task */
	kuid_t		suid;		/* saved UID of the task */
	kgid_t		sgid;		/* saved GID of the task */
	kuid_t		euid;		/* effective UID of the task */
	kgid_t		egid;		/* effective GID of the task */
	kuid_t		fsuid;		/* UID for VFS ops */
	kgid_t		fsgid;		/* GID for VFS ops */
	
	/* ... */
} __randomize_layout;

These fields are all unsigned int fields, and they represent what you would expect - the UID, GID, and a few other less common IDs for other operations (such as the FSUID, which is checked when accessing a file on the file system). As you can expect, overwriting one or more of these fields is likely a pretty desirable goal.

Note the __randomize_layout here at the end! This is a compiler flag that tells it to mix the layout up on each load, making it harder to target the structure!

task_struct

The kernel needs to store information about each running task, and to do this it uses the task_struct structure. Each kernel task has its own instance.

struct task_struct {
    	/* ... */
    
	/*
	 * Pointers to the (original) parent process, youngest child, younger sibling,
	 * older sibling, respectively.  (p->father can be replaced with
	 * p->real_parent->pid)
	 */

	/* Real parent process: */
	struct task_struct __rcu	*real_parent;

	/* Recipient of SIGCHLD, wait4() reports: */
	struct task_struct __rcu	*parent;

	/*
	 * Children/sibling form the list of natural children:
	 */
	struct list_head		children;
	struct list_head		sibling;
	struct task_struct		*group_leader;

	/* ... */    

	/* Objective and real subjective task credentials (COW): */
	const struct cred __rcu		*real_cred;

	/* Effective (overridable) subjective task credentials (COW): */
	const struct cred __rcu		*cred;

    	/* ... */
};

The task_struct instances are stored in a linked list, with a global kernel variable init_task pointing to the first one. Each task_struct then points to the next.

Along with linking data, the task_struct also (more importantly) stores real_cred and cred, which are both pointers to a cred struct. The difference between the two is explained here:

/*
 * The security context of a task
 *
 * The parts of the context break down into two categories:
 *
 *  (1) The objective context of a task.  These parts are used when some other
 *	task is attempting to affect this one.
 *
 *  (2) The subjective context.  These details are used when the task is acting
 *	upon another object, be that a file, a task, a key or whatever.
 *
 * Note that some members of this structure belong to both categories - the
 * LSM security pointer for instance.
 *
 * A task has two security pointers.  task->real_cred points to the objective
 * context that defines that task's actual details.  The objective part of this
 * context is used whenever that task is acted upon.
 *
 * task->cred points to the subjective context that defines the details of how
 * that task is going to act upon another object.  This may be overridden
 * temporarily to point to another security context, but normally points to the
 * same context as task->real_cred.
 */

In effect, cred is the permission when we are trying to act on something, and real_cred when something it trying to act on us. The majority of the time, both will point to the same structure, but a common exception is with setuid executables, which will modify cred but not real_cred.

So, which set of credentials do we want to target with an arbitrary write? Honestly, I'm not entirely sure - it feels as if we want to update cred, as that will change our abilities to read and execute files. Despite that, I have seen writeups overwrite real_cred, so perhaps I am wrong in that - though, again, they usually point to the same struct and therefore would have the same effect.

Once I work it out, I shall update this (TODO!).

prepare_kernel_cred() and commit_creds()

As an alternative to overwriting cred structs in the unpredictable kernel heap, we can call prepare_kernel_cred() to generate a new valid cred struct and commit_creds() to overwrite the real_cred and cred of the current task_struct.

prepare_kernel_cred()

The function can be found here, but there's not much to say - it creates a new cred struct called new then destroys the old. It returns new.

If NULL is passed as the argument, it will return a new set of credentials that match the init_task credentials, which default to root credentials. This is very important, as it means that calling prepare_kernel_cred(0) results in a new set of root creds!

This last part is actually not true on newer kernel versions - check out Debugging the Kernel Module section!

commit_creds()

This function is found here, but ultimately it will update task->real_cred and task->cred to the new credentials:

rcu_assign_pointer(task->real_cred, new);
rcu_assign_pointer(task->cred, new);

Resources and References

Last updated