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, real_cred is the initial credential of the process, and is used by processes acting on the process. cred is the current credential, used to define what the process is allowed to do. We have to keep track of both as some processes care about the initial cred and some about the updated.

An example of caring about the real_cred instead of cred is in the implementation of /proc/$PID/status, which displays the real_cred as the owner of a process, even if privileges are elevated (note that __task_struct is a macro to grab real_cred, confusingly). Conversely, setuid executables will modify cred and not real_cred.

So, which set of credentials do we want to target with an arbitrary write? It will depend on what set is relevant for the purpose, but since you usually want to do be creating new processes (through system or execve), the cred is used. In some cases, real_cred will work too, because it seems as if the pointers initially point to the same struct (though note that this excerpt is not from process creation but copy_process, which is called by the fork syscall, so it could differ for new process creation).

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!

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

Was this helpful?