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.
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!
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
Was this helpful?