DF-0508 / harness.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 | /* * DF-0508 harness: ng_l2cap ConfigReq unknown-option echo inflates m_pkthdr.len * * Source: sys/netgraph7/bluetooth/l2cap/ng_l2cap_evnt.c (DEAD CODE — netgraph7 * is not compiled on DragonFlyBSD master) * * The bug: * get_next_l2cap_opt(:1317-1322): default case returns -3 for unknown * non-hint option WITHOUT validating hdr->length vs actual mbuf data. * (MTU/FLUSH/QOS cases DO validate len < hdr->length at :1285,:1294,:1304) * * ng_l2cap_process_cfg_req(:599-600): * m_adj(m, off - sizeof(hdr)); * m->m_pkthdr.len = sizeof(hdr) + hdr.length; // OVERWRITES with attacker value * * If attacker sends ConfigReq with unknown option type=0x06, length=0xFF, * but only 1 data byte, then m_pkthdr.len is set to sizeof(hdr)+255=257, * while the actual mbuf data is only ~6 bytes. * * The inflated mbuf is echoed in ConfigRsp -> ng_l2cap_lp_send uses * pkthdr.len as the on-air L2CAP+ACL length -> ng_ubt usbd_m_copy_in * reads past mbuf chain end -> kernel heap leaked over Bluetooth to attacker. * * Since netgraph7 is not built on master, this harness demonstrates the * pkthdr.len inflation logic in userspace. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> /* Simplified L2CAP config option header */ struct l2cap_opt_hdr { uint8_t type; uint8_t length; /* attacker-controlled, up to 255 */ } __attribute__((packed)); struct l2cap_cfg_hdr { uint16_t dcid; uint16_t flags; } __attribute__((packed)); /* Replicates the vulnerable path from ng_l2cap_evnt.c */ static int process_cfg_req(uint8_t *mbuf_data, int actual_data_len) { int pkthdr_len = actual_data_len; /* initial correct value */ int off = sizeof(struct l2cap_cfg_hdr); printf(" Initial pkthdr.len = %d (matches actual data)\n", pkthdr_len); /* Parse options */ while (off + sizeof(struct l2cap_opt_hdr) <= actual_data_len) { struct l2cap_opt_hdr *hdr = (struct l2cap_opt_hdr *)(mbuf_data + off); int hint = hdr->type & 0x80; uint8_t type = hdr->type & 0x7f; printf(" Option at off=%d: type=0x%02x length=%d hint=%d\n", off, type, hdr->length, hint ? 1 : 0); switch (type) { case 1: /* MTU */ case 2: /* FLUSH */ case 3: /* QOS */ printf(" -> known option, validates hdr->length <= remaining len\n"); off += sizeof(*hdr) + hdr->length; break; default: if (hint) { printf(" -> unknown hint option, skip\n"); off += sizeof(*hdr) + hdr->length; } else { printf(" -> unknown non-hint option, return -3\n"); /* BUG: no validation of hdr->length vs actual data */ goto unknown_option; } break; } } return 0; unknown_option: /* Replicates ng_l2cap_evnt.c:599-600 */ printf("\n [BUG] m_adj(m, off - sizeof(hdr))\n"); printf(" [BUG] m->m_pkthdr.len = sizeof(hdr) + hdr->length\n"); /* sizeof(l2cap_cfg_hdr) = 4, hdr->length = 255 */ pkthdr_len = sizeof(struct l2cap_cfg_hdr) + 255; printf(" -> pkthdr.len set to %d, but actual data is only %d bytes!\n", pkthdr_len, actual_data_len); printf(" -> INFLATED by %d bytes\n", pkthdr_len - actual_data_len); return pkthdr_len; } int main(void) { printf("=== DF-0508: ng_l2cap ConfigReq unknown-option pkthdr.len inflation ===\n\n"); printf("NOTE: netgraph7 is NOT compiled on DragonFlyBSD master.\n"); printf(" Bug confirmed by source trace; harness demonstrates the logic.\n\n"); /* Craft a ConfigReq: 4-byte cfg header + unknown option type=0x06, length=0xFF, 1 data byte */ uint8_t pkt[16]; memset(pkt, 0, sizeof(pkt)); struct l2cap_cfg_hdr *cfg = (struct l2cap_cfg_hdr *)pkt; cfg->dcid = 0x0040; cfg->flags = 0; int off = sizeof(struct l2cap_cfg_hdr); struct l2cap_opt_hdr *opt = (struct l2cap_opt_hdr *)(pkt + off); opt->type = 0x06; /* unknown non-hint */ opt->length = 0xFF; /* inflated — claims 255 bytes of data */ /* But only 1 byte of actual data follows */ pkt[off + 2] = 0x41; int actual_data_len = off + 3; /* header + opt header + 1 byte */ printf("Crafted ConfigReq: actual data = %d bytes\n", actual_data_len); printf("Unknown option claims length=255 but only 1 byte present\n\n"); int result = process_cfg_req(pkt, actual_data_len); if (result > actual_data_len) { printf("\n BUG CONFIRMED: pkthdr.len (%d) > actual data (%d)\n", result, actual_data_len); printf(" When this mbuf is echoed in ConfigRsp:\n"); printf(" -> ng_l2cap_lp_send sets on-air length from pkthdr.len\n"); printf(" -> usbd_m_copy_in reads %d bytes past mbuf chain end\n", result - actual_data_len); printf(" -> kernel heap leaked to attacker via Bluetooth\n"); } return 0; } |