From cc8ecc5a427b40ee14ec9c420e48fbdac8511c5c Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Fri, 9 May 2025 11:45:37 -0300 Subject: [PATCH] stronger bounds checking --- mongoose.c | 13 +++++---- src/tls_builtin.c | 13 +++++---- test/Makefile | 7 +++++ test/fuzz.c | 42 +++++++++++++++++++++++++--- test/fuzz_tls.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 test/fuzz_tls.c diff --git a/mongoose.c b/mongoose.c index bf6546b8..dea57691 100644 --- a/mongoose.c +++ b/mongoose.c @@ -10999,7 +10999,9 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) { mg_error(c, "not a client hello packet"); return -1; } + if (rio->len < 50) goto fail; msgsz = MG_LOAD_BE16(rio->buf + 3); + if (((uint32_t) msgsz + 4) > rio->len) goto fail; mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz); // store client random memmove(tls->random, rio->buf + 11, sizeof(tls->random)); @@ -11011,10 +11013,11 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) { MG_INFO(("bad session id len")); } cipher_suites_len = MG_LOAD_BE16(rio->buf + 44 + session_id_len); - if (cipher_suites_len > (rio->len - 46 - session_id_len)) goto fail; + if (((uint32_t) cipher_suites_len + 46 + session_id_len) > rio->len) + goto fail; ext_len = MG_LOAD_BE16(rio->buf + 48 + session_id_len + cipher_suites_len); ext = rio->buf + 50 + session_id_len + cipher_suites_len; - if (ext_len > (rio->len - 50 - session_id_len - cipher_suites_len)) goto fail; + if (((unsigned char *) ext + ext_len) > (rio->buf + rio->len)) goto fail; for (j = 0; j < ext_len;) { uint16_t k; uint16_t key_exchange_len; @@ -11026,12 +11029,12 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) { } key_exchange_len = MG_LOAD_BE16(ext + j + 4); key_exchange = ext + j + 6; - if (key_exchange_len > - rio->len - (uint16_t) ((size_t) key_exchange - (size_t) rio->buf)) + if (((size_t) key_exchange_len + + ((size_t) key_exchange - (size_t) rio->buf)) > rio->len) goto fail; for (k = 0; k < key_exchange_len;) { uint16_t m = MG_LOAD_BE16(key_exchange + k + 2); - if (m > (key_exchange_len - k - 4)) goto fail; + if (((uint32_t) m + k + 4) > key_exchange_len) goto fail; if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1] == 0x1d) { memmove(tls->x25519_cli, key_exchange + k + 4, m); mg_tls_drop_record(c); diff --git a/src/tls_builtin.c b/src/tls_builtin.c index 4571075d..df73e92b 100644 --- a/src/tls_builtin.c +++ b/src/tls_builtin.c @@ -570,7 +570,9 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) { mg_error(c, "not a client hello packet"); return -1; } + if (rio->len < 50) goto fail; msgsz = MG_LOAD_BE16(rio->buf + 3); + if (((uint32_t) msgsz + 4) > rio->len) goto fail; mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz); // store client random memmove(tls->random, rio->buf + 11, sizeof(tls->random)); @@ -582,10 +584,11 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) { MG_INFO(("bad session id len")); } cipher_suites_len = MG_LOAD_BE16(rio->buf + 44 + session_id_len); - if (cipher_suites_len > (rio->len - 46 - session_id_len)) goto fail; + if (((uint32_t) cipher_suites_len + 46 + session_id_len) > rio->len) + goto fail; ext_len = MG_LOAD_BE16(rio->buf + 48 + session_id_len + cipher_suites_len); ext = rio->buf + 50 + session_id_len + cipher_suites_len; - if (ext_len > (rio->len - 50 - session_id_len - cipher_suites_len)) goto fail; + if (((unsigned char *) ext + ext_len) > (rio->buf + rio->len)) goto fail; for (j = 0; j < ext_len;) { uint16_t k; uint16_t key_exchange_len; @@ -597,12 +600,12 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) { } key_exchange_len = MG_LOAD_BE16(ext + j + 4); key_exchange = ext + j + 6; - if (key_exchange_len > - rio->len - (uint16_t) ((size_t) key_exchange - (size_t) rio->buf)) + if (((size_t) key_exchange_len + + ((size_t) key_exchange - (size_t) rio->buf)) > rio->len) goto fail; for (k = 0; k < key_exchange_len;) { uint16_t m = MG_LOAD_BE16(key_exchange + k + 2); - if (m > (key_exchange_len - k - 4)) goto fail; + if (((uint32_t) m + k + 4) > key_exchange_len) goto fail; if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1] == 0x1d) { memmove(tls->x25519_cli, key_exchange + k + 4, m); mg_tls_drop_record(c); diff --git a/test/Makefile b/test/Makefile index 5047f307..aec5b69e 100644 --- a/test/Makefile +++ b/test/Makefile @@ -120,6 +120,13 @@ fuzz2: mongoose.c mongoose.h Makefile fuzz.c $(CC) fuzz.c -DMAIN $(OPTS) $(WARN) $(ASAN) $(INCS) -o fuzzer $(RUN) ./fuzzer $(FUZZDATA) +fuzz_tls: ASAN = -fsanitize=fuzzer,signed-integer-overflow,address,undefined +fuzz_tls: mongoose.c mongoose.h Makefile fuzz_tls.c + $(CC) fuzz_tls.c $(OPTS) $(WARN) $(INCS) $(TFLAGS) $(ASAN) -o fuzzer_tls + $(RUN) ./fuzzer_tls -max_len=17000 + + + test: Makefile mongoose.h $(SRCS) tls_multirec/server $(CC) $(SRCS) $(CFLAGS) $(LDFLAGS) -o unit_test ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./unit_test diff --git a/test/fuzz.c b/test/fuzz.c index 1405cc9f..1285ae63 100644 --- a/test/fuzz.c +++ b/test/fuzz.c @@ -82,6 +82,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { mg_json_get(mg_str_n((char *) data, size), "$.a.b", &n); mg_json_get(mg_str_n((char *) data, size), "$[0]", &n); + // Test built-in TCP/IP stack if (size > 0) { struct mg_tcpip_if mif = {.ip = 0x01020304, .mask = 255, @@ -97,15 +98,48 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { memcpy(pkt, data, size); if (size > sizeof(*eth)) { static size_t i; - uint16_t eth_types[] = {0x800, 0x800, 0x806, 0x86dd}; + uint16_t eth_types[] = {0x800, 0x806, 0x86dd}; // IPv4, ARP, IPv6 memcpy(eth->dst, mif.mac, 6); // Set valid destination MAC - eth->type = mg_htons(eth_types[i++]); - if (i >= sizeof(eth_types) / sizeof(eth_types[0])) i = 0; + // 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 = (eth_types[i++]); + // 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 && size > (sizeof(*eth) + sizeof(struct ip) + sizeof(struct udp))) { // 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 (eth->type == mg_htons(0x86dd) && size > (sizeof(*eth) + sizeof(struct ip6))) { // IPv6 + static size_t j; + uint8_t ip6_protos[] = {6, 17}; // TCP, UDP + struct ip6 *ip6 = (struct ip6 *) (eth + 1); + ip6->ver = (ip6->ver & ~0xf0) | (6 << 4); + // send all handled ip6 "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->proto = (ip6_protos[j++]); + if (ip6->proto == 6) { // TCP + } else if (ip6->proto == 17) { // UDP + } + } } mg_tcpip_rx(&mif, pkt, size); - // Test HTTP serving + // Test HTTP serving (via our built-in TCP/IP stack) const char *url = "http://localhost:12345"; struct mg_connection *c = mg_http_connect(&mgr, url, fn, NULL); mg_iobuf_add(&c->recv, 0, data, size); diff --git a/test/fuzz_tls.c b/test/fuzz_tls.c new file mode 100644 index 00000000..8249b6b5 --- /dev/null +++ b/test/fuzz_tls.c @@ -0,0 +1,70 @@ +#define MG_ENABLE_SOCKET 1 +#define MG_ENABLE_LOG 0 +#define MG_ENABLE_LINES 1 +#define MG_ENABLE_TCPIP 0 +#define MG_IO_SIZE (1 * 1024 * 1024) // Big IO size for fast resizes +#define MG_TLS MG_TLS_BUILTIN + +#include "mongoose.c" + +#ifdef __cplusplus +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *, size_t); +#else +int LLVMFuzzerTestOneInput(const uint8_t *, size_t); +#endif +typedef int (*f)(struct mg_connection *); + +f f_[] = { + mg_tls_server_recv_hello, +#if 0 + mg_tls_client_recv_hello, + mg_tls_client_recv_ext, + mg_tls_client_recv_cert, + mg_tls_client_recv_cert_verify +#endif +}; + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + struct mg_connection c_[sizeof(f_)/sizeof(f)], *c = &c_[0]; + struct tls_data tls_[sizeof(f_)/sizeof(f)]; + int i; + if (size == 0) return 0; + mg_log_set(MG_LL_INFO); + memset(c, 0, sizeof(*c)); + c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE; + c->is_tls = c->is_tls_hs = 1; + for (i = 0; i < (int)(sizeof(f_)/sizeof(f)); i++) { + struct mg_iobuf *io; + c = &c_[i]; + io = &c->rtls; + if (i > 0) memcpy(c, &c_[0], sizeof(*c)); // copy from 1st one + if (i > 0) c->is_client = 1; // from 1 on, client functions + memset(&tls_[i], 0, sizeof(struct tls_data)); + if (io->size - io->len < size && !mg_iobuf_resize(io, io->len + size)) { + mg_error(c, "oom"); + return 0; // drop it + } + memcpy(&io->buf[io->len], data, size); + io->len += size; + c->tls = &tls_[i]; + f_[i](&c[i]); + mg_iobuf_free(io); + } + + return 0; +} + +#if defined(MAIN) +int main(int argc, char *argv[]) { + int res = EXIT_FAILURE; + if (argc > 1) { + struct mg_str data = mg_file_read(&mg_fs_posix, argv[1]); + if (data.buf != NULL) { + LLVMFuzzerTestOneInput((uint8_t *) data.buf, data.len); + res = EXIT_SUCCESS; + } + free(data.buf); + } + return res; +} +#endif