From 60c75a22b8ada32754909aa8573d9589a674dc86 Mon Sep 17 00:00:00 2001 From: robert Date: Mon, 21 Sep 2026 15:32:10 +0300 Subject: [PATCH] Validate WebSocket continuation frame sequencing --- mongoose.c | 6 ++++++ mongoose.h | 9 ++++----- src/ws.c | 6 ++++++ test/unit_test.c | 16 +++++++++++++++- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/mongoose.c b/mongoose.c index cf927fba..51b54f57 100644 --- a/mongoose.c +++ b/mongoose.c @@ -27032,6 +27032,12 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data) { op = msg.flags & 15; // MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op, // (int) m.data.len, (int) m.data.len, m.data.buf)); + if ((op == WEBSOCKET_OP_CONTINUE && ofs == 0) || + ((op == WEBSOCKET_OP_TEXT || op == WEBSOCKET_OP_BINARY) && + ofs != 0)) { + mg_error(c, "invalid WS fragment sequence"); + return; + } switch (op) { case WEBSOCKET_OP_CONTINUE: mg_call(c, MG_EV_WS_CTL, &m); diff --git a/mongoose.h b/mongoose.h index bd8e3e7c..13bd8fec 100644 --- a/mongoose.h +++ b/mongoose.h @@ -2838,9 +2838,9 @@ int mg_http_status(const struct mg_http_message *hm); // Streams a POST/PUT body to a callback as it arrives, without buffering it. // Call it from your connection handler on every event. // -// Callback cb(hm, data, &user_data) is called: -// - at start: hm is set, data is NULL. Open the file, set *user_data. -// - per chunk: hm is NULL, data is set. Chunks are multiples of 512 bytes, +// Callback upload_fn(hm, data, user_data) is called: +// - at start: hm is set, data is NULL. Open the file, set user_data. +// - per chunk: hm is NULL, data is set. Chunks are 512 bytes aligned // except the last one. // - at end: both NULL. Close the file. // After the last chunk, "200 ok" is sent and the connection is drained. @@ -2852,8 +2852,7 @@ int mg_http_status(const struct mg_http_message *hm); // mg_http_start_upload(), mg_http_start_ota() // Notes: // Requires Content-Length, chunked bodies are not streamed. user_data is -// c->fn_data. While streaming, c->pfn is NULL, so guard your own -// MG_EV_HTTP_MSG handling with c->pfn != NULL. +// &c->fn_data. While streaming, c->pfn is NULL void mg_http_stream_body(struct mg_connection *c, int ev, void *ev_data, struct mg_str uri_pattern, void (*upload_fn)(struct mg_http_message *, diff --git a/src/ws.c b/src/ws.c index c4ab6b9a..2a2da30f 100644 --- a/src/ws.c +++ b/src/ws.c @@ -182,6 +182,12 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data) { op = msg.flags & 15; // MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op, // (int) m.data.len, (int) m.data.len, m.data.buf)); + if ((op == WEBSOCKET_OP_CONTINUE && ofs == 0) || + ((op == WEBSOCKET_OP_TEXT || op == WEBSOCKET_OP_BINARY) && + ofs != 0)) { + mg_error(c, "invalid WS fragment sequence"); + return; + } switch (op) { case WEBSOCKET_OP_CONTINUE: mg_call(c, MG_EV_WS_CTL, &m); diff --git a/test/unit_test.c b/test/unit_test.c index a6977001..9202073d 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -967,7 +967,7 @@ static void wcb(struct mg_connection *c, int ev, void *ev_data) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_str *wsproto = mg_http_get_header(hm, "Sec-WebSocket-Protocol"); ASSERT(wsproto != NULL); - mg_ws_send(c, "hello", 0, 0); + mg_ws_send(c, "", 0, WEBSOCKET_OP_TEXT); mg_ws_printf(c, WEBSOCKET_OP_BINARY, "%.3s", "boo!!!!"); mg_ws_printf(c, WEBSOCKET_OP_BINARY, "%s", "foobar"); mg_ws_send(c, "", 0, WEBSOCKET_OP_PING); @@ -983,6 +983,15 @@ static void wcb(struct mg_connection *c, int ev, void *ev_data) { } } +static void wbad(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_WS_OPEN) { + mg_ws_send(c, "ABC", 3, WEBSOCKET_OP_CONTINUE); + } else if (ev == MG_EV_CLOSE) { + *(int *) c->fn_data = 1; + } + (void) ev_data; +} + static void ew2(struct mg_connection *c, int ev, void *ev_data) { size_t size = 65 * 1024 + 737; if (ev == MG_EV_WS_OPEN) { @@ -1028,6 +1037,11 @@ static void test_ws(void) { for (i = 0; i < 1000 && done == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(done == 1); + done = 0; + mg_ws_connect(&mgr, url, wbad, &done, NULL); + for (i = 0; i < 30 && done == 0; i++) mg_mgr_poll(&mgr, 1); + ASSERT(done == 1); + mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); }