DF-3339 / duptable_leak.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 | /* * DF-3339 - pfr_add_tables() leaks the second ktable of a duplicate batch * and permanently drifts rs->tables, breaking DIOCRGETTABLES (ENOTTY * "corruption detected") until reboot/module reload. * * Root cause: sys/net/pf/pf_table.c pfr_add_tables():1170-1182 * when the i-th element of the batch is a duplicate of one already * queued in addq, pfr_create_ktable() has ALREADY run (allocating the * ktable + 2 radix heads + incrementing rs->tables) and the * `goto _skip` discards the pointer -- never inserted, never freed. * * requires root (/dev/pf is 0600 root:wheel) - matches DF-0362 threat model. */ #include <sys/types.h> #include <sys/ioctl.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <net/if.h> #include <net/pf/pfvar.h> int main(void) { int fd, rc, i; fd = open("/dev/pf", O_RDWR); if (fd < 0) { printf("open /dev/pf: %s (kldload pf; run as root)\n", strerror(errno)); return (1); } /* * 1) DIOCRADDTABLES with a batch containing the SAME table twice. */ { struct { struct pfioc_table io; struct pfr_table t[2]; } x; memset(&x, 0, sizeof(x)); strlcpy(x.t[0].pfrt_name, "leakdup1", sizeof(x.t[0].pfrt_name)); x.t[1] = x.t[0]; x.io.pfrio_buffer = x.t; x.io.pfrio_esize = sizeof(struct pfr_table); x.io.pfrio_size = 2; errno = 0; rc = ioctl(fd, DIOCRADDTABLES, &x.io); printf("[1] DIOCRADDTABLES([leakdup1,leakdup1]) rc=%d " "errno=%d nadd=%d\n", rc, errno, x.io.pfrio_nadd); } /* * 2) Same thing with PFR_FLAG_DUMMY (leaks on the dummy path too). */ { struct { struct pfioc_table io; struct pfr_table t[2]; } x; memset(&x, 0, sizeof(x)); strlcpy(x.t[0].pfrt_name, "leakdup2", sizeof(x.t[0].pfrt_name)); x.t[1] = x.t[0]; x.io.pfrio_buffer = x.t; x.io.pfrio_esize = sizeof(struct pfr_table); x.io.pfrio_size = 2; x.io.pfrio_flags = PFR_FLAG_DUMMY; errno = 0; rc = ioctl(fd, DIOCRADDTABLES, &x.io); printf("[2] DIOCRADDTABLES(dummy,[leakdup2,leakdup2]) rc=%d " "errno=%d nadd=%d\n", rc, errno, x.io.pfrio_nadd); } /* * 3) DIOCRGETTABLES: pf_main_ruleset.tables is now 4 (1 real + * 3 leaked) but the RB tree holds 1 -> "corruption detected", * ENOTTY. This persists until pf.ko is unloaded. */ { struct { struct pfioc_table io; struct pfr_table t[16]; } g; memset(&g, 0, sizeof(g)); g.io.pfrio_buffer = g.t; g.io.pfrio_esize = sizeof(struct pfr_table); g.io.pfrio_size = 16; errno = 0; rc = ioctl(fd, DIOCRGETTABLES, &g.io); printf("[3] DIOCRGETTABLES rc=%d errno=%d (%s) size=%d\n", rc, errno, errno == ENOTTY ? "ENOTTY -- corruption " "detected, table listing broken" : strerror(errno), g.io.pfrio_size); if (rc == 0) for (i = 0; i < g.io.pfrio_size; i++) printf(" table[%d] = %s\n", i, g.t[i].pfrt_name); } return (0); } |