From abbd4749e1db4afc4c5b310ffabb591efe01ff73 Mon Sep 17 00:00:00 2001 From: Mounika HJ Date: Sat, 1 Aug 2026 08:26:33 +0530 Subject: [PATCH 1/2] fix: Bound decode_compressed to the length zlib actually wrote --- src/dpp/etf.cpp | 13 +++++++------ src/unittest/test.cpp | 33 +++++++++++++++++++++++++++++++++ src/unittest/test.h | 1 + 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/dpp/etf.cpp b/src/dpp/etf.cpp index 2b4c25f657..a7aab67ae7 100644 --- a/src/dpp/etf.cpp +++ b/src/dpp/etf.cpp @@ -492,21 +492,22 @@ json etf_parser::decode_tuple_large() { json etf_parser::decode_compressed() { const uint32_t uncompressedSize = read_32_bits(); - unsigned long sourceSize = uncompressedSize; - std::vector outBuffer; - outBuffer.reserve(uncompressedSize); - const int ret = uncompress((Bytef*)outBuffer.data(), &sourceSize, (const unsigned char*)(data + offset), (uLong)(size - offset)); + /* zlib takes this as the capacity of outBuffer and returns the number of bytes it actually wrote */ + unsigned long destinationSize = uncompressedSize; + std::vector outBuffer(uncompressedSize); + const int ret = uncompress((Bytef*)outBuffer.data(), &destinationSize, (const unsigned char*)(data + offset), (uLong)(size - offset)); - offset += sourceSize; if (ret != Z_OK) { throw dpp::parse_exception(err_etf, "ETF compressed value: decompresson error"); } + /* uncompress() was handed the rest of the buffer as its input, so the term ends there */ + offset = size; uint8_t* old_data = data; size_t old_size = size; size_t old_offset = offset; data = outBuffer.data(); - size = uncompressedSize; + size = destinationSize; offset = 0; json j = inner_parse(); data = old_data; diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 3fbb0c961f..b5fea681b5 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -25,6 +25,7 @@ #include #include #include +#include /** * @brief global lock for log output @@ -266,6 +267,38 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b dpp::base64_encode(reinterpret_cast("vwxyz12"), 7) == "dnd4eXoxMg==" ); + set_test(ETF_COMPRESSED_SHORT, false); + { + /* An ett_binary announcing 4080 bytes of payload that were never encoded */ + const uint8_t truncated_term[]{'m', 0x00, 0x00, 0x0F, 0xF0}; + uLongf deflated_length = compressBound((uLong)sizeof(truncated_term)); + std::vector deflated_term(deflated_length); + bool etf_result = compress(deflated_term.data(), &deflated_length, truncated_term, (uLong)sizeof(truncated_term)) == Z_OK; + + /* Version, ett_compressed, then a declared uncompressed size of 4096 */ + std::string payload; + payload.push_back((char)131); + payload.push_back('P'); + payload.push_back(0); + payload.push_back(0); + payload.push_back(0x10); + payload.push_back(0); + payload.append((const char*)deflated_term.data(), deflated_length); + + /* zlib only writes five bytes, so the announced payload is not there and + * decoding must yield null rather than a 4080 byte string assembled from + * memory it never filled in. + */ + dpp::etf_parser etf; + try { + etf_result = etf_result && etf.parse(payload).is_null(); + } + catch (const dpp::parse_exception&) { + etf_result = false; + } + set_test(ETF_COMPRESSED_SHORT, etf_result); + } + dpp::http_connect_info hci; set_test(HOSTINFO, false); diff --git a/src/unittest/test.h b/src/unittest/test.h index eba614b7c0..67b41e51a8 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -148,6 +148,7 @@ DPP_TEST(MD_ESC_1, "Markdown escaping (ignore code block contents)", tf_offline) DPP_TEST(MD_ESC_2, "Markdown escaping (escape code block contents)", tf_offline); DPP_TEST(URLENC, "URL encoding", tf_offline); DPP_TEST(BASE64ENC, "Base 64 encoding", tf_offline); +DPP_TEST(ETF_COMPRESSED_SHORT, "etf_parser: compressed term shorter than its declared size", tf_offline); DPP_TEST(COMPARISON, "manged object comparison", tf_offline); DPP_TEST(CHANNELCACHE, "find_channel()", tf_online); DPP_TEST(CHANNELTYPES, "channel type flags", tf_online); From d7532cdd9179c1d00c227f110f3d7d8f512c9631 Mon Sep 17 00:00:00 2001 From: Mounika HJ Date: Sat, 1 Aug 2026 14:53:21 +0530 Subject: [PATCH 2/2] fix: Keep the original offset handling and drop zlib from the unit test --- src/dpp/etf.cpp | 3 +-- src/unittest/test.cpp | 35 ++++++++++++++--------------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/dpp/etf.cpp b/src/dpp/etf.cpp index a7aab67ae7..5a6d3ccf46 100644 --- a/src/dpp/etf.cpp +++ b/src/dpp/etf.cpp @@ -497,11 +497,10 @@ json etf_parser::decode_compressed() { std::vector outBuffer(uncompressedSize); const int ret = uncompress((Bytef*)outBuffer.data(), &destinationSize, (const unsigned char*)(data + offset), (uLong)(size - offset)); + offset += destinationSize; if (ret != Z_OK) { throw dpp::parse_exception(err_etf, "ETF compressed value: decompresson error"); } - /* uncompress() was handed the rest of the buffer as its input, so the term ends there */ - offset = size; uint8_t* old_data = data; size_t old_size = size; diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index b5fea681b5..f2772b0430 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -25,7 +25,6 @@ #include #include #include -#include /** * @brief global lock for log output @@ -269,29 +268,23 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(ETF_COMPRESSED_SHORT, false); { - /* An ett_binary announcing 4080 bytes of payload that were never encoded */ - const uint8_t truncated_term[]{'m', 0x00, 0x00, 0x0F, 0xF0}; - uLongf deflated_length = compressBound((uLong)sizeof(truncated_term)); - std::vector deflated_term(deflated_length); - bool etf_result = compress(deflated_term.data(), &deflated_length, truncated_term, (uLong)sizeof(truncated_term)) == Z_OK; - - /* Version, ett_compressed, then a declared uncompressed size of 4096 */ - std::string payload; - payload.push_back((char)131); - payload.push_back('P'); - payload.push_back(0); - payload.push_back(0); - payload.push_back(0x10); - payload.push_back(0); - payload.append((const char*)deflated_term.data(), deflated_length); - - /* zlib only writes five bytes, so the announced payload is not there and - * decoding must yield null rather than a 4080 byte string assembled from - * memory it never filled in. + /* Version, ett_compressed, a declared uncompressed size of 4096, then a + * deflate stream that only inflates to five bytes: an ett_binary announcing + * 4080 bytes of payload that were never encoded. */ + const char truncated_term[]{ + (char)131, 'P', 0x00, 0x00, 0x10, 0x00, + (char)0x78, (char)0x9c, (char)0xcb, 0x65, 0x60, (char)0xe0, + (char)0xff, 0x00, 0x00, 0x03, 0x34, 0x01, 0x6d + }; + + /* The announced payload is not there, so decoding must yield null rather + * than a 4080 character string assembled from memory that was never written. + */ + bool etf_result; dpp::etf_parser etf; try { - etf_result = etf_result && etf.parse(payload).is_null(); + etf_result = etf.parse(std::string(truncated_term, sizeof(truncated_term))).is_null(); } catch (const dpp::parse_exception&) { etf_result = false;