Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package nng for openSUSE:Factory checked in at 2026-10-17 21:24:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/nng (Old) and /work/SRC/openSUSE:Factory/.nng.new.3698736 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "nng" Sat Oct 17 21:24:36 2026 rev:11 rq:1385258 version:1.12.4 Changes: -------- --- /work/SRC/openSUSE:Factory/nng/nng.changes 2026-09-11 18:05:15.343676677 +0200 +++ /work/SRC/openSUSE:Factory/.nng.new.3698736/nng.changes 2026-10-17 21:24:38.803988817 +0200 @@ -1,0 +2,20 @@ +Fri Oct 16 12:51:38 UTC 2026 - Martin Pluskal <[email protected]> + +- Update to version 1.12.4 (no upstream release notes published + for 1.12.4; changes mined from the v1.12.2...v1.12.4 range): + * 1.12.3: fix a potential overflow in the HTTP chunk-size + parser that let a malicious server crash a client + (gh#nanomsg/nng#2284). + * 1.12.3: refuse unreasonable allocation sizes instead of + attempting them. + * 1.12.3: harden WebSocket against sender-driven + allocations: cap allocations and frame counts to avoid + quadratic compute growth from many small or empty frames. + * 1.12.4: reject unsafe HTTP request targets. + * 1.12.4: validate directory handler paths, further + hardening the directory serving fixed in 1.12.2. + * No CVE has been assigned to any of these fixes, and no + open VUL bug exists for nng. The ABI soversion is + unchanged at 1. + +------------------------------------------------------------------- Old: ---- nng-1.12.2.tar.gz New: ---- nng-1.12.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ nng.spec ++++++ --- /var/tmp/diff_new_pack.agDLB2/_old 2026-10-17 21:24:39.478016936 +0200 +++ /var/tmp/diff_new_pack.agDLB2/_new 2026-10-17 21:24:39.479016977 +0200 @@ -19,7 +19,7 @@ %define sover 1 Name: nng -Version: 1.12.2 +Version: 1.12.4 Release: 0 Summary: Nanomsg NG - brokerless messaging License: MIT ++++++ nng-1.12.2.tar.gz -> nng-1.12.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nng-1.12.2/include/nng/nng.h new/nng-1.12.4/include/nng/nng.h --- old/nng-1.12.2/include/nng/nng.h 2026-08-22 22:58:48.000000000 +0200 +++ new/nng-1.12.4/include/nng/nng.h 2026-08-30 20:21:33.000000000 +0200 @@ -1,5 +1,5 @@ // -// Copyright 2025 Staysail Systems, Inc. <[email protected]> +// Copyright 2026 Staysail Systems, Inc. <[email protected]> // Copyright 2018 Capitar IT Group BV <[email protected]> // // This software is supplied under the terms of the MIT License, a @@ -58,7 +58,7 @@ // may not necessarily match the ABI versions. #define NNG_MAJOR_VERSION 1 #define NNG_MINOR_VERSION 12 -#define NNG_PATCH_VERSION 2 +#define NNG_PATCH_VERSION 4 // if non-empty (i.e. "pre"), this is a pre-release #define NNG_RELEASE_SUFFIX "" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nng-1.12.2/src/core/message.c new/nng-1.12.4/src/core/message.c --- old/nng-1.12.2/src/core/message.c 2026-08-22 22:58:48.000000000 +0200 +++ new/nng-1.12.4/src/core/message.c 2026-08-30 20:21:33.000000000 +0200 @@ -396,6 +396,11 @@ nni_msg *m; int rv; + // Avoid overflows, and reserve the upper four bits + // for use by SP stream transports. + if ((uint64_t)sz > 0x0fffffffffffffffull) { + return (NNG_EMSGSIZE); + } if ((m = NNI_ALLOC_STRUCT(m)) == NULL) { return (NNG_ENOMEM); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nng-1.12.2/src/supplemental/http/http_chunk.c new/nng-1.12.4/src/supplemental/http/http_chunk.c --- old/nng-1.12.2/src/supplemental/http/http_chunk.c 2026-08-22 22:58:48.000000000 +0200 +++ new/nng-1.12.4/src/supplemental/http/http_chunk.c 2026-08-30 20:21:33.000000000 +0200 @@ -44,6 +44,7 @@ struct nng_http_chunks { nni_list cl_chunks; size_t cl_maxsz; + size_t cl_total; // total size of all chunks size_t cl_size; // parsed size (so far) size_t cl_line; // bytes since last newline enum chunk_state cl_state; @@ -100,12 +101,7 @@ size_t nni_http_chunks_size(nni_http_chunks *cl) { - size_t tot = 0; - nni_http_chunk *ch; - NNI_LIST_FOREACH (&cl->cl_chunks, ch) { - tot += ch->c_size; - } - return (tot); + return (cl->cl_total); } size_t @@ -123,22 +119,28 @@ static int chunk_ingest_len(nni_http_chunks *cl, char c) { - if (isdigit(c)) { - cl->cl_size *= 16; - cl->cl_size += (c - '0'); + size_t digit; + + if (isdigit((unsigned char) c)) { + digit = (size_t) (c - '0'); } else if ((c >= 'A') && (c <= 'F')) { - cl->cl_size *= 16; - cl->cl_size += (c - 'A') + 10; + digit = (size_t) ((c - 'A') + 10); } else if ((c >= 'a') && (c <= 'f')) { - cl->cl_size *= 16; - cl->cl_size += (c - 'a') + 10; + digit = (size_t) ((c - 'a') + 10); } else if (c == ';') { cl->cl_state = CS_EXT; + return (0); } else if (c == '\r') { cl->cl_state = CS_CR; + return (0); } else { return (NNG_EPROTO); } + if (cl->cl_size > ((SIZE_MAX - digit) / 16)) { + return (NNG_EMSGSIZE); + } + cl->cl_size *= 16; + cl->cl_size += digit; return (0); } @@ -147,7 +149,7 @@ { if (c == '\r') { cl->cl_state = CS_CR; - } else if (!isprint(c)) { + } else if (!isprint((unsigned char) c)) { return (NNG_EPROTO); } return (0); @@ -166,8 +168,11 @@ cl->cl_state = CS_TRLR; return (0); } - if ((cl->cl_maxsz > 0) && - ((nni_http_chunks_size(cl) + cl->cl_size) > cl->cl_maxsz)) { + if ((cl->cl_size > (SIZE_MAX - 2)) || + (cl->cl_size > (SIZE_MAX - cl->cl_total)) || + ((cl->cl_maxsz > 0) && + ((cl->cl_total > cl->cl_maxsz) || + (cl->cl_size > (cl->cl_maxsz - cl->cl_total))))) { return (NNG_EMSGSIZE); } if ((chunk = NNI_ALLOC_STRUCT(chunk)) == NULL) { @@ -186,6 +191,7 @@ chunk->c_size = cl->cl_size; chunk->c_alloc = cl->cl_size + 2; chunk->c_resid = chunk->c_alloc; + cl->cl_total += cl->cl_size; nni_list_append(&cl->cl_chunks, chunk); return (0); @@ -198,7 +204,7 @@ cl->cl_state = CS_TRLRCR; return (0); } - if (!isprint(c)) { + if (!isprint((unsigned char) c)) { return (NNG_EPROTO); } cl->cl_line++; @@ -226,7 +232,7 @@ int rv; switch (cl->cl_state) { case CS_INIT: - if (!isalnum(c)) { + if (!isalnum((unsigned char) c)) { rv = NNG_EPROTO; break; } @@ -281,6 +287,7 @@ if ((chunk->c_data[chunk->c_size] != '\r') || (chunk->c_data[chunk->c_size + 1] != '\n')) { + *lenp = n; return (NNG_EPROTO); } chunk->c_resid = 0; @@ -315,8 +322,10 @@ break; case CS_DATA: - if ((rv = chunk_ingest_data(cl, src + i, n - i, &cnt)) != - 0) { + cnt = 0; + if ((rv = chunk_ingest_data( + cl, src + i, n - i, &cnt)) != 0) { + *lenp = i + cnt; return (rv); } i += cnt; @@ -326,6 +335,7 @@ // All others character by character parse through // the state machine grinder. if ((rv = chunk_ingest_char(cl, src[i])) != 0) { + *lenp = i; return (rv); } i++; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nng-1.12.2/src/supplemental/http/http_msg.c new/nng-1.12.4/src/supplemental/http/http_msg.c --- old/nng-1.12.2/src/supplemental/http/http_msg.c 2026-08-22 22:58:48.000000000 +0200 +++ new/nng-1.12.4/src/supplemental/http/http_msg.c 2026-08-30 20:21:33.000000000 +0200 @@ -785,6 +785,14 @@ *version = '\0'; version++; + // A request-target cannot contain a fragment or a backslash. Fragments + // are only meaningful in URI references, and a backslash can become a + // directory separator on Windows. Reject them here rather than allowing + // a file handler to interpret them as part of a local path. + if ((strchr(uri, '#') != NULL) || (strchr(uri, '\\') != NULL)) { + return (NNG_EPROTO); + } + if ((rv = nni_url_canonify_uri(&canon_uri, uri)) != 0) { return (rv); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nng-1.12.2/src/supplemental/http/http_msg_test.c new/nng-1.12.4/src/supplemental/http/http_msg_test.c --- old/nng-1.12.2/src/supplemental/http/http_msg_test.c 2026-08-22 22:58:48.000000000 +0200 +++ new/nng-1.12.4/src/supplemental/http/http_msg_test.c 2026-08-30 20:21:33.000000000 +0200 @@ -20,6 +20,8 @@ char plain[] = "GET /../../outside.txt HTTP/1.1\r\n\r\n"; char encoded[] = "GET /%2e%2e/%2E%2e/outside.txt HTTP/1.1\r\n\r\n"; + char fragment[] = "GET /#/../../outside.txt HTTP/1.1\r\n\r\n"; + char backslash[] = "GET /..\\..\\outside.txt HTTP/1.1\r\n\r\n"; size_t len; NUTS_PASS(nni_http_req_alloc(&req, NULL)); @@ -31,9 +33,47 @@ NUTS_PASS(nni_http_req_parse(req, encoded, strlen(encoded), &len)); NUTS_MATCH(nni_http_req_get_uri(req), "/outside.txt"); nni_http_req_free(req); + + NUTS_PASS(nni_http_req_alloc(&req, NULL)); + NUTS_FAIL(nni_http_req_parse(req, fragment, strlen(fragment), &len), + NNG_EPROTO); + nni_http_req_free(req); + + NUTS_PASS(nni_http_req_alloc(&req, NULL)); + NUTS_FAIL(nni_http_req_parse(req, backslash, strlen(backslash), &len), + NNG_EPROTO); + nni_http_req_free(req); +} + +static void +test_http_chunk_size_overflow(void) +{ + nni_http_chunks *chunks = NULL; + char body[] = "10000000000000000\r\n"; + size_t len = 0; + + NUTS_PASS(nni_http_chunks_init(&chunks, 0)); + NUTS_FAIL(nni_http_chunks_parse(chunks, body, strlen(body), &len), + NNG_EMSGSIZE); + nni_http_chunks_free(chunks); +} + +static void +test_http_chunk_alloc_overflow(void) +{ + nni_http_chunks *chunks = NULL; + char body[] = "ffffffffffffffff\r\n"; + size_t len = 0; + + NUTS_PASS(nni_http_chunks_init(&chunks, 0)); + NUTS_FAIL(nni_http_chunks_parse(chunks, body, strlen(body), &len), + NNG_EMSGSIZE); + nni_http_chunks_free(chunks); } NUTS_TESTS = { { "http request URI canonicalization", test_http_req_canonify_uri }, + { "http chunk size overflow", test_http_chunk_size_overflow }, + { "http chunk allocation overflow", test_http_chunk_alloc_overflow }, { NULL, NULL }, }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nng-1.12.2/src/supplemental/http/http_server.c new/nng-1.12.4/src/supplemental/http/http_server.c --- old/nng-1.12.2/src/supplemental/http/http_server.c 2026-08-22 22:58:48.000000000 +0200 +++ new/nng-1.12.4/src/supplemental/http/http_server.c 2026-08-30 20:21:33.000000000 +0200 @@ -1547,6 +1547,22 @@ return; } + // The request parser rejects these characters, but check again before + // building a filesystem path in case a request reaches this handler by + // another route. A fragment is not part of an HTTP request-target, and a + // backslash is a directory separator on Windows. + if ((strchr(uri + len, '#') != NULL) || + (strchr(uri + len, '\\') != NULL)) { + if ((rv = nni_http_res_alloc_error( + &res, NNG_HTTP_STATUS_BAD_REQUEST)) != 0) { + nni_aio_finish_error(aio, rv); + return; + } + nni_aio_set_output(aio, 0, res); + nni_aio_finish(aio, 0, 0); + return; + } + // simple worst case is every character in path is a separator // It's never actually that bad, because we we have /<something>/. pnsz = (strlen(path) + strlen(uri) + 2) * strlen(NNG_PLATFORM_DIR_SEP); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nng-1.12.2/src/supplemental/websocket/websocket.c new/nng-1.12.4/src/supplemental/websocket/websocket.c --- old/nng-1.12.2/src/supplemental/websocket/websocket.c 2026-08-22 22:58:48.000000000 +0200 +++ new/nng-1.12.4/src/supplemental/websocket/websocket.c 2026-08-30 20:21:33.000000000 +0200 @@ -32,6 +32,10 @@ #define WS_DEF_MAXRXFRAME (1U << 20) // 1MB Frame size (recv) #define WS_DEF_MAXTXFRAME (1U << 16) // 64KB Frame size (send) +#ifndef WS_DEF_MAXFRAMES +#define WS_DEF_MAXFRAMES (1 << 12) // 4K frames in a single message +#endif + // Alias for checking the prefix of a string. #define startswith(s, t) (strncmp(s, t, strlen(t)) == 0) @@ -62,6 +66,8 @@ nni_list recvq; nni_list txq; nni_list rxq; + int rxq_len; + size_t rxq_size; ws_frame *txframe; ws_frame *rxframe; nni_aio *txaio; // physical aios @@ -947,17 +953,31 @@ static void ws_read_frame_cb(nni_ws *ws, ws_frame *frame) { + bool message_done = false; + switch (frame->op) { case WS_CONT: if (!ws->inmsg) { ws_close(ws, WS_CLOSE_PROTOCOL_ERR); return; } + if (ws->rxq_len >= WS_DEF_MAXFRAMES) { + ws_close(ws, WS_CLOSE_TOO_BIG); + return; + } if (frame->final) { ws->inmsg = false; + message_done = true; } ws->rxframe = NULL; - nni_list_append(&ws->rxq, frame); + if (frame->len == 0) { + // drop empty middle frames + ws_frame_fini(frame); + } else { + nni_list_append(&ws->rxq, frame); + ws->rxq_len++; + ws->rxq_size += frame->len; + } break; case WS_TEXT: if (!ws->recv_text) { @@ -972,8 +992,13 @@ } if (!frame->final) { ws->inmsg = true; + } else { + message_done = true; } ws->rxframe = NULL; + ws->rxq_size = frame->len; + ws->rxq_len++; + NNI_ASSERT(ws->rxq_len == 1); nni_list_append(&ws->rxq, frame); break; @@ -1010,6 +1035,10 @@ return; } + if (message_done) { + ws->rxq_len = 0; + ws->rxq_size = 0; + } ws_read_finish(ws); } @@ -1101,12 +1130,7 @@ // length of the message has not exceeded our recvmax. // (Protect against an infinite stream of small messages!) if ((!ws->isstream) && (ws->recvmax > 0)) { - size_t totlen = frame->len; - ws_frame *fr2; - NNI_LIST_FOREACH (&ws->rxq, fr2) { - totlen += fr2->len; - } - if (totlen > ws->recvmax) { + if (ws->rxq_size + frame->len > ws->recvmax) { ws_close(ws, WS_CLOSE_TOO_BIG); nni_mtx_unlock(&ws->mtx); return; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nng-1.12.2/src/supplemental/websocket/websocket_test.c new/nng-1.12.4/src/supplemental/websocket/websocket_test.c --- old/nng-1.12.2/src/supplemental/websocket/websocket_test.c 2026-08-22 22:58:48.000000000 +0200 +++ new/nng-1.12.4/src/supplemental/websocket/websocket_test.c 2026-08-30 20:21:33.000000000 +0200 @@ -13,6 +13,8 @@ #include <nuts.h> +#define WS_OPT_MSGMODE "ws:msgmode" + void test_websocket_wildcard(void) { @@ -340,6 +342,108 @@ nng_stream_dialer_free(d); } +void +test_websocket_frame_limit(void) +{ + nng_stream_listener *l = NULL; + nng_stream_dialer *d = NULL; + nng_stream *c = NULL; + nng_stream *s = NULL; + nng_aio *daio = NULL; + nng_aio *laio = NULL; + nng_aio *raio = NULL; + nng_aio *saio = NULL; + nng_msg *msg; + char url[64]; + uint16_t port; + const size_t maxframes = 1U << 12; + int rv; + + NUTS_PASS(nng_aio_alloc(&daio, NULL, NULL)); + NUTS_PASS(nng_aio_alloc(&laio, NULL, NULL)); + NUTS_PASS(nng_aio_alloc(&raio, NULL, NULL)); + NUTS_PASS(nng_aio_alloc(&saio, NULL, NULL)); + nng_aio_set_timeout(daio, 5000); + nng_aio_set_timeout(laio, 5000); + nng_aio_set_timeout(raio, 5000); + nng_aio_set_timeout(saio, 5000); + + for (int i = 0; i < 256; i++) { + port = nuts_next_port(); + (void) snprintf( + url, sizeof(url), "ws://127.0.0.1:%d/test", port); + NUTS_PASS(nng_stream_listener_alloc(&l, url)); + NUTS_PASS(nng_stream_dialer_alloc(&d, url)); + NUTS_PASS(nng_stream_listener_set_bool( + l, WS_OPT_MSGMODE, true)); + NUTS_PASS(nng_stream_dialer_set_bool( + d, WS_OPT_MSGMODE, true)); + NUTS_PASS(nng_stream_listener_set_size( + l, NNG_OPT_WS_SENDMAXFRAME, 1)); + rv = nng_stream_listener_listen(l); + if (rv != NNG_EADDRINUSE) { + break; + } + nng_stream_listener_free(l); + nng_stream_dialer_free(d); + } + NUTS_PASS(rv); + + nng_stream_listener_accept(l, laio); + nng_stream_dialer_dial(d, daio); + nng_aio_wait(laio); + nng_aio_wait(daio); + NUTS_PASS(nng_aio_result(laio)); + NUTS_PASS(nng_aio_result(daio)); + s = nng_aio_get_output(laio, 0); + c = nng_aio_get_output(daio, 0); + + // Two messages at the limit must be accepted. Receiving the first + // one verifies that the accounting is reset before receiving the next. + for (int i = 0; i < 2; i++) { + NUTS_PASS(nng_msg_alloc(&msg, maxframes)); + memset(nng_msg_body(msg), 'A' + i, maxframes); + nng_aio_set_msg(saio, msg); + nng_stream_recv(c, raio); + nng_stream_send(s, saio); + nng_aio_wait(saio); + nng_aio_wait(raio); + NUTS_PASS(nng_aio_result(saio)); + NUTS_PASS(nng_aio_result(raio)); + msg = nng_aio_get_msg(raio); + NUTS_TRUE(nng_msg_len(msg) == maxframes); + nng_aio_set_msg(raio, NULL); + nng_msg_free(msg); + } + + // One more fragment than the limit must close the receiver. + NUTS_PASS(nng_msg_alloc(&msg, maxframes + 1)); + memset(nng_msg_body(msg), 'X', maxframes + 1); + nng_aio_set_msg(saio, msg); + nng_stream_recv(c, raio); + nng_stream_send(s, saio); + nng_aio_wait(raio); + NUTS_FAIL(nng_aio_result(raio), NNG_ECLOSED); + nng_aio_wait(saio); + NUTS_TRUE((nng_aio_result(saio) == 0) || + (nng_aio_result(saio) == NNG_ECLOSED)); + if ((msg = nng_aio_get_msg(saio)) != NULL) { + nng_aio_set_msg(saio, NULL); + nng_msg_free(msg); + } + + nng_stream_close(c); + nng_stream_free(c); + nng_stream_close(s); + nng_stream_free(s); + nng_aio_free(saio); + nng_aio_free(raio); + nng_aio_free(laio); + nng_aio_free(daio); + nng_stream_listener_free(l); + nng_stream_dialer_free(d); +} + typedef struct recv_state { nng_stream *c; int total; @@ -515,5 +619,6 @@ { "websocket conn properties", test_websocket_conn_props }, { "websocket fragmentation", test_websocket_fragmentation }, { "websocket text mode", test_websocket_text_mode }, + { "websocket frame limit", test_websocket_frame_limit }, { NULL, NULL }, };
