From 699067ff58ce887d25591e9cc8838fa8614507db Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Mon, 29 Jun 2026 17:19:27 -0300 Subject: [PATCH] fuzz 802.1Q and PPP[oE] --- mongoose.h | 2 +- src/l2.h | 2 +- test/fuzz.c | 265 +++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 225 insertions(+), 44 deletions(-) diff --git a/mongoose.h b/mongoose.h index 30c3a0db..4c879fa3 100644 --- a/mongoose.h +++ b/mongoose.h @@ -4629,7 +4629,7 @@ bool mg_wifi_ap_stop(void); #if MG_ENABLE_TCPIP // no config defaults to 0 => Ethernet -enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE }; +enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE, MG_TCPIP_L2_USER }; #if defined(__DCC__) #pragma pack(1) diff --git a/src/l2.h b/src/l2.h index 7a80f736..83b09691 100644 --- a/src/l2.h +++ b/src/l2.h @@ -6,7 +6,7 @@ #if MG_ENABLE_TCPIP // no config defaults to 0 => Ethernet -enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE }; +enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE, MG_TCPIP_L2_USER }; #if defined(__DCC__) #pragma pack(1) diff --git a/test/fuzz.c b/test/fuzz.c index 9e3876fa..dd1a2537 100644 --- a/test/fuzz.c +++ b/test/fuzz.c @@ -23,6 +23,52 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { } } +// deduplicate layer-3 mocking among layer-2s +struct fuzz_l3_state { + size_t ip_proto, udp_port; + size_t ip6_next, icmp6_type; +}; + +static void fuzz_ip4(void *buf, size_t len, struct fuzz_l3_state *st) { + if (len >= sizeof(struct ip)) { + uint8_t ip_protos[] = {1, 6, 17}; // ICMP, TCP, UDP + struct ip *ip4 = (struct ip *) buf; + ip4->ver = (uint8_t) ((ip4->ver & ~0xf0) | (4 << 4)); + // send all handled IP protos, then 2 random ones + if (st->ip_proto >= (sizeof(ip_protos) / sizeof(ip_protos[0]) + 2)) st->ip_proto = 0; + if (st->ip_proto < (sizeof(ip_protos) / sizeof(ip_protos[0]))) ip4->proto = ip_protos[st->ip_proto++]; + if (ip4->proto == 1) { // ICMP + } else if (ip4->proto == 6) { // TCP + } else if (ip4->proto == 17 && len >= sizeof(struct ip) + sizeof(struct udp)) { // UDP + uint16_t udp_ports[] = {67, 68}; // DHCP server and client + struct udp *udp = (struct udp *) (ip4 + 1); + // send all handled UDP ports, then 2 random ones + if (st->udp_port >= (sizeof(udp_ports) / sizeof(udp_ports[0]) + 2)) st->udp_port = 0; + if (st->udp_port < (sizeof(udp_ports) / sizeof(udp_ports[0]))) udp->dport = mg_htons(udp_ports[st->udp_port++]); + } + } +} + +static void fuzz_ip6(void *buf, size_t len, struct fuzz_l3_state *st) { + if (len >= sizeof(struct ip6)) { + uint8_t ip6_protos[] = {6, 17, 58}; // TCP, UDP, ICMPv6 + struct ip6 *ip6 = (struct ip6 *) buf; + ip6->ver = (uint8_t) ((ip6->ver & ~0xf0) | (6 << 4)); + // send all handled IPv6 "next headers", then 2 random ones + if (st->ip6_next >= (sizeof(ip6_protos) / sizeof(ip6_protos[0]) + 2)) st->ip6_next = 0; + if (st->ip6_next < (sizeof(ip6_protos) / sizeof(ip6_protos[0]))) ip6->next = ip6_protos[st->ip6_next++]; + if (ip6->next == 6) { // TCP + } else if (ip6->next == 17) { // UDP + } else if (ip6->next == 58 && len >= sizeof(struct ip6) + sizeof(struct icmp6)) { // ICMPv6 + uint8_t icmp6_types[] = {128, 134, 135, 136}; // Echo Request, RA, NS, NA + struct icmp6 *icmp6 = (struct icmp6 *) (ip6 + 1); + // send all handled ICMPv6 types, then 2 random ones + if (st->icmp6_type >= (sizeof(icmp6_types) / sizeof(icmp6_types[0]) + 2)) st->icmp6_type = 0; + if (st->icmp6_type < (sizeof(icmp6_types) / sizeof(icmp6_types[0]))) icmp6->type = icmp6_types[st->icmp6_type++]; + } + } +} + int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { mg_log_set(MG_LL_INFO); @@ -94,6 +140,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { // Test built-in TCP/IP stack if (size > 0) { + static size_t t; struct mg_tcpip_if mif = {.ip = 1, .mask = 255, .gw = 1, @@ -109,6 +156,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { #endif .driver = &mg_tcpip_driver_mock}; struct mg_mgr mgr; + + if (t >= MG_TCPIP_L2_USER) t = 0; // defined in l2.h + mif.l2type = (enum mg_l2type) t++; // configure to a L2 type + mg_mgr_init(&mgr); mg_tcpip_init(&mgr, &mif); @@ -116,56 +167,186 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { void *pkt = malloc(size); struct eth *eth = (struct eth *) pkt; memcpy(pkt, data, size); - if (size > sizeof(*eth)) { - static size_t i; + if ((mif.l2type == MG_TCPIP_L2_ETH || mif.l2type == MG_TCPIP_L2_PPPoE) && size > sizeof(*eth)) { + static size_t i, v; + static struct fuzz_l3_state l3; + uint8_t *ethpay = (uint8_t *) (eth + 1); + size_t ethhdrsz = sizeof(*eth); + uint16_t etype; // eth_types[] exists in l2_eth.c memcpy(eth->dst, mif.mac, 6); // Set valid destination MAC // send all handled eth types, then 2 random ones if (i >= (sizeof(eth_types) / sizeof(eth_types[0]) + 2)) i = 0; if (i < (sizeof(eth_types) / sizeof(eth_types[0]))) eth->type = mg_htons(eth_types[i++]); + etype = mg_ntohs(eth->type); + if (v >= 10) v = 0; // 802.1Q tag 3 out of 10 frames + if (v < 3) mif.l2data.eth.vlan_id = 123; + if (size > sizeof(*eth) + sizeof(struct qtag)) { + struct qtag *qtag = (struct qtag *) ð->type; + if (v == 0) { + // v=0: configured for VLAN 123, untagged frame, discard path + } else if (v == 1) { + // v=1: configured for VLAN 123, tagged VLAN 123, accept path + qtag->tpid = mg_htons(0x8100); + qtag->tci = mg_htons(123); + MG_STORE_BE16(qtag + 1, etype); + ethhdrsz += sizeof(*qtag); + ethpay += sizeof(*qtag); + } else if (v == 2) { + // v=2: configured for VLAN 123, tagged random VLAN, mismatch path + qtag->tpid = mg_htons(0x8100); + MG_STORE_BE16(qtag + 1, etype); + ethhdrsz += sizeof(*qtag); + ethpay += sizeof(*qtag); + } else if (v == 3) { + // v=3: unconfigured VLAN, tagged frame, discard path + qtag->tpid = mg_htons(0x8100); + MG_STORE_BE16(qtag + 1, etype); + ethhdrsz += sizeof(*qtag); + ethpay += sizeof(*qtag); + } // else unconfigured VLAN, untagged frame, normal path + } + ++v; + if (etype == 0x8863 && size > (ethhdrsz + sizeof(struct pppoe))) { // PPPoE disc + static size_t j; + uint8_t pppoe_codes[] = {MG_PPPoE_PADO, MG_PPPoE_PADS, MG_PPPoE_PADT}; // defined in l2_ppp.c + struct pppoe *pppoe = (struct pppoe *) ethpay; + pppoe->vertype = 0x11; + // send all handled PPPoE codes, then 2 random ones + if (j >= (sizeof(pppoe_codes) / sizeof(pppoe_codes[0]) + 2)) j = 0; + if (j < (sizeof(pppoe_codes) / sizeof(pppoe_codes[0]))) pppoe->code = (pppoe_codes[j++]); + if (pppoe->code == MG_PPPoE_PADO) { + static size_t k; + // send id=0, then 2 random ids + if (k >= 3) k = 0; + if (k == 0) pppoe->id = 0; + ++k; + } else if (pppoe->code == MG_PPPoE_PADS) { + static size_t k; + // send id=x, then 2 random ids + if (k >= 3) k = 0; + if (k == 0) pppoe->id = 0x1234; + ++k; + } else if (pppoe->code == MG_PPPoE_PADT) { // keep separate so they do not correlate too much + static size_t k; + // send id=x, then 2 random ids + if (k >= 3) k = 0; + if (k == 0) pppoe->id = 0x1234; + ++k; + } + } else if (etype == 0x8864 && size > (ethhdrsz + sizeof(struct pppoe))) { // PPPoE sess + // PPP rx is covered via HDLC too; PPPoE tx wrapping may remain unexercised. + struct pppoe *pppoe = (struct pppoe *) ethpay; + static size_t k, l; + // send code=0, then 2 random codes + if (k >= 3) k = 0; + if (k == 0) { + static size_t m; + pppoe->code = 0; + // send id=x, then 2 random ids + if (m >= 3) m = 0; + if (m == 0) pppoe->id = 0x1234; + ++m; + } + ++k; + // set s_state to valid and invalid + if (l >= 2) l = 0; + if (l == 0) s_state = MG_PPPoE_ST_SESS; // defined in l2_ppp.c + if (l == 1) s_state = MG_PPPoE_ST_REQ; + ++l; + // PPP exercised in other block + } // build proper layer-3 datagrams, to be able to exercise layers above - if (eth->type == mg_htons(0x800) && size > (sizeof(*eth) + sizeof(struct ip))) { // IPv4 - static size_t j; - uint8_t ip_protos[] = {1, 6, 17}; // ICMP, TCP, UDP - struct ip *ip4 = (struct ip *) (eth + 1); - ip4->ver = (ip4->ver & ~0xf0) | (4 << 4); - // send all handled IP protos, then 2 random ones - if (j >= (sizeof(ip_protos) / sizeof(ip_protos[0]) + 2)) j = 0; - if (j < (sizeof(ip_protos) / sizeof(ip_protos[0]))) ip4->proto = (ip_protos[j++]); - if (ip4->proto == 1) { // ICMP - } else if (ip4->proto == 6) { // TCP - } else if (ip4->proto == 17) { // UDP - if (size > (sizeof(*eth) + sizeof(struct ip) + sizeof(struct udp))) { - static size_t k; - uint16_t udp_ports[] = {67, 68}; // DHCP server and client - struct udp *udp = (struct udp *) (ip4 + 1); - // send all handled UDP ports, then 2 random ones - if (k >= (sizeof(udp_ports) / sizeof(udp_ports[0]) + 2)) k = 0; - if (k < (sizeof(udp_ports) / sizeof(udp_ports[0]))) udp->dport = mg_htons(udp_ports[k++]); - } - } - } else if (eth->type == mg_htons(0x806)) { // ARP + else if (etype == 0x800 && size > (ethhdrsz + sizeof(struct ip))) { // IPv4 + fuzz_ip4(ethpay, size - ethhdrsz, &l3); + } else if (etype == 0x806) { // ARP - } else if (eth->type == mg_htons(0x86dd) && size > (sizeof(*eth) + sizeof(struct ip6))) { // IPv6 - static size_t j; - uint8_t ip6_protos[] = {6, 17, 58}; // TCP, UDP, ICMPv6 - struct ip6 *ip6 = (struct ip6 *) (eth + 1); - ip6->ver = (ip6->ver & ~0xf0) | (6 << 4); - // send all handled IPv6 "next headers", then 2 random ones - if (j >= (sizeof(ip6_protos) / sizeof(ip6_protos[0]) + 2)) j = 0; - if (j < (sizeof(ip6_protos) / sizeof(ip6_protos[0]))) ip6->next = (ip6_protos[j++]); - if (ip6->next == 6) { // TCP - } else if (ip6->next == 17) { // UDP - } else if (ip6->next == 58) { // ICMPv6 - if (size >= (sizeof(*eth) + sizeof(struct ip6) + sizeof(struct icmp6))) { - static size_t k; - uint8_t icmp6_types[] = {128, 134, 135, 136}; // Echo Request, RA, NS, NA - struct icmp6 *icmp6 = (struct icmp6 *) (ip6 + 1); - // send all handled ICMPv6 types, then 2 random ones - if (k >= (sizeof(icmp6_types) / sizeof(icmp6_types[0]) + 2)) k = 0; - if (k < (sizeof(icmp6_types) / sizeof(icmp6_types[0]))) icmp6->type = icmp6_types[k++]; - } + } else if (etype == 0x86dd && size > (ethhdrsz + sizeof(struct ip6))) { // IPv6 + fuzz_ip6(ethpay, size - ethhdrsz, &l3); + } + } else if (mif.l2type == MG_TCPIP_L2_PPP && size > (sizeof(struct hdlc_) + sizeof(struct ppp) + 2)) { + static size_t i; + static struct fuzz_l3_state l3; + uint16_t ppp_protos[] = { + MG_PPP_PROTO_LCP, MG_PPP_PROTO_IPCP, MG_PPP_PROTO_IP, MG_PPP_PROTO_IPV6CP, MG_PPP_PROTO_IPV6 + }; + struct hdlc_ *hdlc = (struct hdlc_ *) pkt; + struct ppp *ppp = (struct ppp *) (hdlc + 1); + size_t len = size - sizeof(*hdlc) - sizeof(*ppp) - 2; + hdlc->addr = MG_PPP_ADDR; + hdlc->ctrl = MG_PPP_CTRL; + s_lcpup = true; + // send all handled PPP protocols, then 2 random ones + if (i >= (sizeof(ppp_protos) / sizeof(ppp_protos[0]) + 2)) i = 0; + if (i < (sizeof(ppp_protos) / sizeof(ppp_protos[0]))) ppp->proto = mg_htons(ppp_protos[i++]); + if (ppp->proto == mg_htons(MG_PPP_PROTO_LCP) && len >= sizeof(struct lcp)) { + static size_t j, k; + uint8_t lcp_codes[] = {MG_PPP_LCP_CFG_REQ, MG_PPP_LCP_CFG_ACK, MG_PPP_LCP_CFG_TERM_REQ, MG_PPP_LCP_ECHO_REQ}; + struct lcp *lcp = (struct lcp *) (ppp + 1); + // send all handled LCP codes, then 2 random ones + if (j >= (sizeof(lcp_codes) / sizeof(lcp_codes[0]) + 2)) j = 0; + if (j < (sizeof(lcp_codes) / sizeof(lcp_codes[0]))) lcp->code = lcp_codes[j++]; + // send valid length, then 2 random lengths + if (k >= 3) k = 0; + if (k == 0) lcp->len = mg_htons((uint16_t) sizeof(*lcp)); + ++k; + } else if (ppp->proto == mg_htons(MG_PPP_PROTO_IPCP) && len >= 10) { + static size_t j, k, m; + uint8_t ipcp_codes[] = {MG_PPP_IPCP_CFG_REQ, MG_PPP_IPCP_CFG_ACK, MG_PPP_IPCP_CFG_NACK, MG_PPP_IPCP_CFG_REJECT}; + struct ipcp *ipcp = (struct ipcp *) (ppp + 1); + uint8_t *opts = (uint8_t *) (ipcp + 1); + // send all handled IPCP codes, then 2 random ones + if (j >= (sizeof(ipcp_codes) / sizeof(ipcp_codes[0]) + 2)) j = 0; + if (j < (sizeof(ipcp_codes) / sizeof(ipcp_codes[0]))) ipcp->code = ipcp_codes[j++]; + // send valid length, then 2 random lengths + if (m >= 3) m = 0; + if (m == 0) ipcp->len = mg_htons(10); + ++m; + // send valid IPADDR option, then 2 random option types + if (k >= 3) k = 0; + if (k == 0) { + static size_t l; + opts[0] = MG_PPP_IPCP_OPT_IPADDR; + // for valid option type, send valid length, then random length + if (l >= 2) l = 0; + if (l == 0) opts[1] = 6; + ++l; } + ++k; + } else if (ppp->proto == mg_htons(MG_PPP_PROTO_IP) && len >= sizeof(struct ip)) { + fuzz_ip4(ppp + 1, len, &l3); + } else if (ppp->proto == mg_htons(MG_PPP_PROTO_IPV6CP) && len >= 14) { + static size_t j, k, m; + uint8_t ipv6cp_codes[] = {MG_PPP_IPV6CP_CFG_REQ, MG_PPP_IPV6CP_CFG_ACK, MG_PPP_IPV6CP_CFG_NACK, MG_PPP_IPV6CP_CFG_REJECT}; + struct ipv6cp *ipv6cp = (struct ipv6cp *) (ppp + 1); + uint8_t *opts = (uint8_t *) (ipv6cp + 1); + // send all handled IPV6CP codes, then 2 random ones + if (j >= (sizeof(ipv6cp_codes) / sizeof(ipv6cp_codes[0]) + 2)) j = 0; + if (j < (sizeof(ipv6cp_codes) / sizeof(ipv6cp_codes[0]))) ipv6cp->code = ipv6cp_codes[j++]; + // send valid length, then 2 random lengths + if (m >= 3) m = 0; + if (m == 0) ipv6cp->len = mg_htons(14); + ++m; + // send valid IFCID option, then 2 random option types + if (k >= 3) k = 0; + if (k == 0) { + static size_t l; + opts[0] = MG_PPP_IPV6CP_OPT_IFCID; + // for valid option type, send valid length, then random length + if (l >= 2) l = 0; + if (l == 0) { + static size_t o; + opts[1] = 10; + // for valid option length, send non-zero IFCID, then 2 random IFCIDs + if (o >= 3) o = 0; + if (o == 0) opts[9] = 1; + ++o; + } + ++l; + } + ++k; + } else if (ppp->proto == mg_htons(MG_PPP_PROTO_IPV6) && len >= sizeof(struct ip6)) { + fuzz_ip6(ppp + 1, len, &l3); } }