DF-0457 / 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 | /* * DF-0457 harness: ng_pppoe unchecked ph->length -> heap OOB read in get_tag/scan_tags * * Source: sys/netgraph/pppoe/ng_pppoe.c * - :925 length = ntohs(wh->ph.length) โ NEVER validated vs actual mbuf payload * - :271-276 next_tag: end = &ph->tag[0] + ntohs(ph->length) * - :283-307 get_tag: walks (pt+1)<=end, dereferencing up to ph->length bytes past tag start * - :952-968 m_pullup + m_len==pkthdr.len only check contiguity, NOT bounds * * If a PPPoE discovery frame has ph->length=65535 but only 20 bytes of actual data, * get_tag walks 65535 bytes past &ph->tag[0], reading adjacent kernel heap. * * Additionally: scan_tags(:1621-1658) + insert_tag(PTT_RELAY_SID) + make_packet(:376) * copies up to 1500 bytes (ETHER_MAX_LEN) of the inflated buffer into the PADO/PADR * response, leaking kernel heap bytes to the attacker. * * This is a REMOTE adjacent-network vulnerability. Trigger: send a crafted PPPoE * discovery frame to a host running ng_pppoe on an ethernet interface. * * This harness replicates get_tag's tag-walk logic with a crafted ph->length > actual * data size to demonstrate the OOB read. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <arpa/inet.h> /* Simplified PPPoE header (from sys/netgraph/pppoe/ng_pppoe.h) */ struct pppoe_tag { uint16_t tag_type; uint16_t tag_len; } __attribute__((packed)); struct pppoe_hdr { uint8_t vertype; uint8_t code; uint16_t session; uint16_t length; /* attacker-controlled, NOT validated vs mbuf */ struct pppoe_tag tag[0]; } __attribute__((packed)); /* Replicates get_tag (ng_pppoe.c:283-307) */ static const struct pppoe_tag *get_tag(const struct pppoe_hdr *ph, uint16_t idx, const char *actual_end) { /* BUG: end computed from ph->length, NOT actual data size */ const char *const end = (const char *)&ph->tag[0] + ntohs(ph->length); const char *ptn; const struct pppoe_tag *pt = &ph->tag[0]; int steps = 0; int oob_steps = 0; while ((const char *)(pt + 1) <= end) { steps++; if ((const char *)(pt + 1) > actual_end) { oob_steps++; } ptn = (const char *)(pt + 1) + ntohs(pt->tag_len); if (ptn > end) return NULL; if (pt->tag_type == idx) { printf(" Found tag type=0x%04x at step %d%s\n", ntohs(pt->tag_type), steps, oob_steps > 0 ? " [OOB!]" : ""); return pt; } pt = (const struct pppoe_tag *)ptn; if (steps > 100) break; /* safety */ } printf(" Tag walk: %d steps total, %d were OOB reads\n", steps, oob_steps); return NULL; } int main(void) { printf("=== DF-0457: ng_pppoe unchecked ph->length -> heap OOB read ===\n\n"); /* Simulate a PPPoE discovery frame in a kernel mbuf. * Actual data: 20 bytes (pppoe_hdr + 1 tag with 2 bytes data). * Attacker sets ph->length = 2000 (way past actual data). */ uint8_t mbuf_data[64]; /* the actual mbuf */ uint8_t adjacent_heap[4096]; /* simulate adjacent kernel heap */ memset(mbuf_data, 0xAA, sizeof(mbuf_data)); memset(adjacent_heap, 0xBB, sizeof(adjacent_heap)); /* Lay out a valid-ish PPPoE PADI */ struct pppoe_hdr *ph = (struct pppoe_hdr *)mbuf_data; ph->vertype = 0x11; ph->code = 0x09; /* PADI */ ph->session = 0; ph->length = htons(2000); /* INFLATED โ actual payload is only ~12 bytes */ /* One real tag: Service-Name (0x0101) with 0 length */ ph->tag[0].tag_type = htons(0x0101); ph->tag[0].tag_len = htons(0); /* Actual data in mbuf is just the header + 1 tag = 6 + 4 = 10 bytes */ const char *actual_end = (const char *)&ph->tag[0] + 4; /* 4 bytes of real tag data */ printf("Actual mbuf payload: %ld bytes after tag[0] start\n", (long)(actual_end - (const char *)&ph->tag[0])); printf("ph->length claims: %d bytes after tag[0] start\n\n", ntohs(ph->length)); printf("Searching for tag 0x0101 (Service-Name):\n"); const struct pppoe_tag *found = get_tag(ph, 0x0101, actual_end); printf("\nSearching for tag 0xFFFF (non-existent โ forces full walk):\n"); found = get_tag(ph, 0xFFFF, actual_end); printf("\n BUG CONFIRMED: get_tag walks up to ph->length bytes past tag[0],\n"); printf(" reading adjacent kernel heap. The m_pullup+contiguity check at\n"); printf(" ng_pppoe.c:952-968 does NOT bound ph->length vs actual data.\n"); printf("\n Additionally, scan_tags + insert_tag(PTT_RELAY_SID) + make_packet\n"); printf(" copies up to 1500 bytes of the inflated buffer into the response,\n"); printf(" leaking kernel heap to the attacker via Relay-Session-Id.\n"); return 0; } |