Modify process credentials in Linux kernel

Linux uses a massive, singular structure to manage information about each running process, referred to as task_struct and defined in include/linux/sched.h. It contains the current status of the process (runnable, stopped, etc.), process identifier (PID), scheduling information, its parent, siblings and children and many, many other. Below is a short snippet of the struct, but if you’re curious you should really scroll through the entire structure, maybe something will peak your interest: task_struct

struct task_struct {
	struct thread_info        thread_info;

	/* -1 unrunnable, 0 runnable, >0 stopped: */
	volatile long             state;

	void                      *stack;
	atomic_t                  usage;
    // ...
	int                       prio;
	int                       static_prio;
	int                       normal_prio;
	unsigned int              rt_priority;

	struct sched_info         sched_info;

	struct list_head          tasks;

	pid_t                     pid;
	pid_t                     tgid;

	/* Process credentials: */

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

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

As you can see above, the task_struct also references the credentials structure which contains information about privileges, capabilities and permissions of each process (task_struct->cred).

The credentials structure, defined in include/linux/cred.h, stores information about the owner of the process, the process’ capabilities and various other. Especially alluring are the traditional UNIX credentials (UID, GID, etc. members of the structure). Modifying these results in a privilege elevation.

/*
 * 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.
 *
 * 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.
 */
struct cred {
	atomic_t	usage;
	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 */
	unsigned	securebits;	/* SUID-less security management */
	kernel_cap_t	cap_inheritable; /* caps our children can inherit */
	kernel_cap_t	cap_permitted;	/* caps we're permitted */
	kernel_cap_t	cap_effective;	/* caps we can actually use */
	kernel_cap_t	cap_bset;	/* capability bounding set */
	kernel_cap_t	cap_ambient;	/* Ambient capability set */
    // ...
};

Now we know what we are dealing with, so we can start implementing our routine to modify it.

First, we create new kuid and kgid structures. These are wrappers for storing and managing UIDs and GIDs in the kernel (implemented with typedefs, see include/linux/uidgid.h). The only parameter for these wrappers is the new UID / GID, below we use “0” to set root permissions.

kuid_t kuid = KUIDT_INIT(0);
kgid_t kgid = KGIDT_INIT(0);

Next we create a new credentials structure with prepare_creds() and assign the kuid and kgid variables we created before to all the UID and GID properties. According to kernel/cred.c:

A task’s creds shouldn’t generally be modified directly, therefore this function is used to prepare a new copy, which the caller then modifies.

struct cred *new_cred = prepare_creds();
if (new_cred == NULL) {
  printk("Failed to prepare new credentials\n");
  return -ENOMEM;
}

new_cred->uid = kuid;
new_cred->gid = kgid;
new_cred->euid = kuid;
new_cred->egid = kgid;

Finally we commit the changes with commit_creds(). In the background the kernel then validates the new credential set and installs it to the current task, using a read-copy-update (RCU) mechanism to prevent any race-conditions. It will also notify the scheduler and other subscribers of the changes. Interesting to note here: the commit_creds() function cannot fail, therefore we don’t have to check its return value.

commit_creds(new_cred);

Putting it all together, we obtain the following routine for modifying process credentials in the Linux kernel. In the code snippet below, we first retrieve the context of the currently running task, though any other process context can be accessed, too (e.g. by PID).

struct task_struct *my_task;
struct cred *new_cred;
kuid_t kuid = KUIDT_INIT(0);
kgid_t kgid = KGIDT_INIT(0);

/* get context of currently active task */
my_task = get_current();
if (my_task == NULL) {
  printk("Failed to get current task info.\n");
  return -1;
}

/* change privileges */
new_cred = prepare_creds();
if (new_cred == NULL) {
  printk("Failed to prepare new credentials\n");
  return -ENOMEM;
}

new_cred->uid = kuid;
new_cred->gid = kgid;
new_cred->euid = kuid;
new_cred->egid = kgid;

commit_creds(new_cred);
return 0;

Since the code above utilizes kernel function and modifies kernel structures, it obviously has be run from within the kernel itself or from a kernel module.


Further References: