Your message dated Mon, 4 Aug 2025 15:17:15 +0200 with message-id <3eed0c14-5a46-41b8-bd53-6fa3aa5302e8@debian.org> and subject line Re: Bug#1110393: unblock: cpp-httplib/0.18.7-2 has caused the Debian Bug report #1110393, regarding unblock: cpp-httplib/0.18.7-2 to be marked as done. This means that you claim that the problem has been dealt with. If this is not the case it is now your responsibility to reopen the Bug report if necessary, and/or fix the problem forthwith. (NB: If you are a system administrator and have no idea what this message is talking about, this may indicate a serious mail system misconfiguration somewhere. Please contact owner@bugs.debian.org immediately.) -- 1110393: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1110393 Debian Bug Tracking System Contact owner@bugs.debian.org with problems
--- Begin Message ---
- To: <submit@bugs.debian.org>
- Subject: unblock: cpp-httplib/0.18.7-2
- From: "Andrea Pappacoda" <tachi@debian.org>
- Date: Mon, 04 Aug 2025 14:05:44 +0200
- Message-id: <[🔎] DBTMOV7710AS.3PO0EYZ9FC8XH@debian.org>
Package: release.debian.org Control: affects -1 + src:cpp-httplib X-Debbugs-Cc: cpp-httplib@packages.debian.org User: release.debian.org@packages.debian.org Usertags: unblock Severity: normal Please unblock package cpp-httplib [ Reason ]I have cherry picked and backported fixes for three CVEs, one of which is for an easy to perform DoS attack for the server component of cpp-httplib.[ Impact ]If the unblock isn't granted, trixie will ship with an already unsecure version of the library. The server component of cpp-httplib isn't as widely used as its HTTP client, though.[ Tests ]New upstream tests were added which test against the old security bugs. No pre-existing tests are regressed from this change. I have also checked that the attack PoC scripts are unable to exploit the vulnerabilities after the backport.[ Risks ]After having reviewed the code, I think the risks are pretty low. Most code changes are test additions. All code changes are internal implementation details.[ Checklist ] [x] all changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in testing [ Other info ]For some reason, the mentioned CVEs are wrong, in the sense that the vulnerabilities they describe are messed up. Please look at the corresponding GitHub Advisories to read the correct descriptions.I have not closed bug #1109340 in the changelog since it also contains a less severe HTTP smuggling vulnerability. Fixing it as well is probably not doable without introducing breaking changes.I have pushed my changes to the debian/trixie Git packaging branch: <https://salsa.debian.org/debian/cpp-httplib/-/tree/debian/trixie>.Bye! unblock cpp-httplib/0.18.7-2diff -Nru cpp-httplib-0.18.7/debian/changelog cpp-httplib-0.18.7/debian/changelog --- cpp-httplib-0.18.7/debian/changelog 2025-03-11 18:18:06.000000000 +0100 +++ cpp-httplib-0.18.7/debian/changelog 2025-08-04 13:04:36.000000000 +0200 @@ -1,3 +1,28 @@ +cpp-httplib (0.18.7-2) unstable; urgency=high + + * fix CVE-2025-46728 (DoS via unbounded request line length). + While this patch intended to enforce request body size limits for + chunked Transfer-Encoding, it actually adds size limits for a unique + lines read from HTTP requests, solving another kind of DoS. + See the GHSA-px83-72rx-v57c GitHub advisory for more details. + Thanks to Yang Wang for the patch! + Closes: #1104926 + + * fix CVE-2025-52887 (Unlimited number of HTTP headers causes memory leak). + This patch adds a limit to the number of headers which + can be passed in an HTTP request, mitigating a possible DoS due to memory + exhaustion. + See bug #1109340 and the GHSA-xjhg-gf59-p92h GitHub advisory for more + details. + + * fix CVE-2025-53629 (Unbounded Memory Allocation in Chunked Requests). + This patch complements the fix for CVE-2025-46728, actually solving + memory exhaustion attacks via chucked HTTP requests. + See bug #1109340 and the GHSA-qjmq-h3cc-qv6w GitHub advisory for more + details. + + -- Andrea Pappacoda <tachi@debian.org> Mon, 04 Aug 2025 13:04:36 +0200 + cpp-httplib (0.18.7-1) unstable; urgency=medium * Update to new upstream version 0.18.7. diff -Nru cpp-httplib-0.18.7/debian/gbp.conf cpp-httplib-0.18.7/debian/gbp.conf --- cpp-httplib-0.18.7/debian/gbp.conf 2025-03-11 18:18:06.000000000 +0100 +++ cpp-httplib-0.18.7/debian/gbp.conf 2025-08-04 13:04:36.000000000 +0200 @@ -1,7 +1,7 @@ [DEFAULT] dist = DEP14 -debian-branch = debian/latest +debian-branch = debian/trixie upstream-branch = upstream/latest pristine-tar = True pristine-tar-commit = True diff -Nru cpp-httplib-0.18.7/debian/patches/0001-httplib.h-fix-CVE-2025-46728-DoS-via-unbounded-reque.patch cpp-httplib-0.18.7/debian/patches/0001-httplib.h-fix-CVE-2025-46728-DoS-via-unbounded-reque.patch --- cpp-httplib-0.18.7/debian/patches/0001-httplib.h-fix-CVE-2025-46728-DoS-via-unbounded-reque.patch 1970-01-01 01:00:00.000000000 +0100 +++ cpp-httplib-0.18.7/debian/patches/0001-httplib.h-fix-CVE-2025-46728-DoS-via-unbounded-reque.patch 2025-08-04 13:04:36.000000000 +0200 @@ -0,0 +1,90 @@ +From: Ville Vesilehto <ville@vesilehto.fi> +Date: Sat, 3 May 2025 11:39:01 +0300 +Subject: httplib.h: fix CVE-2025-46728 (DoS via unbounded request line + lenght) + +While this patch intended to enforce request body size limits for +chunked Transfer-Encoding, it actually adds size limits for a unique +lines read from HTTP requests, solving another kind of DoS. + +Author: Ville Vesilehto <ville@vesilehto.fi> +Origin: upstream, https://github.com/yhirose/cpp-httplib/commit/7b752106ac42bd5b907793950d9125a0972c8e8e +Bug-Debian: https://bugs.debian.org/1104926 +Bug: https://github.com/yhirose/cpp-httplib/security/advisories/GHSA-px83-72rx-v57c +Forwarded: not-needed +Reviewed-By: Yang Wang <yang.wang@windriver.com> +Reviewed-By: Andrea Pappacoda <tachi@debian.org> + +Closes: #1104926 +--- + httplib.h | 9 +++++++++ + test/test.cc | 15 +++++++++++++++ + 2 files changed, 24 insertions(+) + +diff --git a/httplib.h b/httplib.h +index eb592d2..ec81a75 100644 +--- a/httplib.h ++++ b/httplib.h +@@ -141,6 +141,10 @@ + #define CPPHTTPLIB_LISTEN_BACKLOG 5 + #endif + ++#ifndef CPPHTTPLIB_MAX_LINE_LENGTH ++#define CPPHTTPLIB_MAX_LINE_LENGTH 32768 ++#endif ++ + /* + * Headers + */ +@@ -2961,6 +2965,11 @@ inline bool stream_line_reader::getline() { + #endif + + for (size_t i = 0;; i++) { ++ if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) { ++ // Treat exceptionally long lines as an error to ++ // prevent infinite loops/memory exhaustion ++ return false; ++ } + char byte; + auto n = strm_.read(&byte, 1); + +diff --git a/test/test.cc b/test/test.cc +index b69be5c..fe7151e 100644 +--- a/test/test.cc ++++ b/test/test.cc +@@ -42,6 +42,9 @@ const int PORT = 1234; + const string LONG_QUERY_VALUE = string(25000, '@'); + const string LONG_QUERY_URL = "/long-query-value?key=" + LONG_QUERY_VALUE; + ++const string TOO_LONG_QUERY_VALUE = string(35000, '@'); ++const string TOO_LONG_QUERY_URL = "/too-long-query-value?key=" + TOO_LONG_QUERY_VALUE; ++ + const std::string JSON_DATA = "{\"hello\":\"world\"}"; + + const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB +@@ -2837,6 +2840,11 @@ protected: + EXPECT_EQ(LONG_QUERY_URL, req.target); + EXPECT_EQ(LONG_QUERY_VALUE, req.get_param_value("key")); + }) ++ .Get("/too-long-query-value", ++ [&](const Request &req, Response & /*res*/) { ++ EXPECT_EQ(TOO_LONG_QUERY_URL, req.target); ++ EXPECT_EQ(TOO_LONG_QUERY_VALUE, req.get_param_value("key")); ++ }) + .Get("/array-param", + [&](const Request &req, Response & /*res*/) { + EXPECT_EQ(3u, req.get_param_value_count("array")); +@@ -3611,6 +3619,13 @@ TEST_F(ServerTest, LongQueryValue) { + EXPECT_EQ(StatusCode::UriTooLong_414, res->status); + } + ++TEST_F(ServerTest, TooLongQueryValue) { ++ auto res = cli_.Get(TOO_LONG_QUERY_URL.c_str()); ++ ++ ASSERT_FALSE(res); ++ EXPECT_EQ(Error::Read, res.error()); ++} ++ + TEST_F(ServerTest, TooLongHeader) { + Request req; + req.method = "GET"; diff -Nru cpp-httplib-0.18.7/debian/patches/0002-httplib.h-fix-CVE-2025-52887-Unlimited-number-of-htt.patch cpp-httplib-0.18.7/debian/patches/0002-httplib.h-fix-CVE-2025-52887-Unlimited-number-of-htt.patch --- cpp-httplib-0.18.7/debian/patches/0002-httplib.h-fix-CVE-2025-52887-Unlimited-number-of-htt.patch 1970-01-01 01:00:00.000000000 +0100 +++ cpp-httplib-0.18.7/debian/patches/0002-httplib.h-fix-CVE-2025-52887-Unlimited-number-of-htt.patch 2025-08-04 13:04:36.000000000 +0200 @@ -0,0 +1,185 @@ +From: yhirose <yhirose@users.noreply.github.com> +Date: Tue, 24 Jun 2025 07:56:00 -0400 +Subject: httplib.h: fix CVE-2025-52887 (Unlimited number of http header + fields causes memory leak) + +This patch adds a limit to the number of headers which can be passed in +an HTTP request, mitigating a possible DoS due to memory exhaustion. + +Author: Yuji Hirose <yuji.hirose.bug@gmail.com> +Origin: upstream, https://github.com/yhirose/cpp-httplib/commit/28dcf379e82a2cdb544d812696a7fd46067eb7f9 +Bug-Debian: https://bugs.debian.org/1109340 +Bug: https://github.com/yhirose/cpp-httplib/security/advisories/GHSA-xjhg-gf59-p92h +Forwarded: not-needed +Reviewed-By: Andrea Pappacoda <tachi@debian.org> +--- + httplib.h | 17 ++++++++++++++ + test/test.cc | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 91 insertions(+) + +diff --git a/httplib.h b/httplib.h +index ec81a75..0a5c6fc 100644 +--- a/httplib.h ++++ b/httplib.h +@@ -86,6 +86,10 @@ + #define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 + #endif + ++#ifndef CPPHTTPLIB_HEADER_MAX_COUNT ++#define CPPHTTPLIB_HEADER_MAX_COUNT 100 ++#endif ++ + #ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT + #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 + #endif +@@ -4249,6 +4253,8 @@ inline bool read_headers(Stream &strm, Headers &headers) { + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + ++ size_t header_count = 0; ++ + for (;;) { + if (!line_reader.getline()) { return false; } + +@@ -4269,6 +4275,9 @@ inline bool read_headers(Stream &strm, Headers &headers) { + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + ++ // Check header count limit ++ if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; } ++ + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + +@@ -4278,6 +4287,8 @@ inline bool read_headers(Stream &strm, Headers &headers) { + })) { + return false; + } ++ ++ header_count++; + } + + return true; +@@ -4379,9 +4390,13 @@ inline bool read_content_chunked(Stream &strm, T &x, + // chuncked transfer coding data without the final CRLF. + if (!line_reader.getline()) { return true; } + ++ size_t trailer_header_count = 0; + while (strcmp(line_reader.ptr(), "\r\n") != 0) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + ++ // Check trailer header count limit ++ if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; } ++ + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; +@@ -4391,6 +4406,8 @@ inline bool read_content_chunked(Stream &strm, T &x, + x.headers.emplace(key, val); + }); + ++ trailer_header_count++; ++ + if (!line_reader.getline()) { return false; } + } + +diff --git a/test/test.cc b/test/test.cc +index fe7151e..e29be7d 100644 +--- a/test/test.cc ++++ b/test/test.cc +@@ -3,7 +3,11 @@ + #include <signal.h> + + #ifndef _WIN32 ++#include <arpa/inet.h> + #include <curl/curl.h> ++#include <netinet/in.h> ++#include <sys/socket.h> ++#include <unistd.h> + #endif + #include <gtest/gtest.h> + +@@ -3680,6 +3684,50 @@ TEST_F(ServerTest, TooLongHeader) { + EXPECT_EQ(StatusCode::OK_200, res->status); + } + ++TEST_F(ServerTest, HeaderCountAtLimit) { ++ // Test with headers just under the 100 limit ++ httplib::Headers headers; ++ ++ // Add 95 custom headers (the client will add Host, User-Agent, Accept, etc.) ++ // This should keep us just under the 100 header limit ++ for (int i = 0; i < 95; i++) { ++ std::string name = "X-Test-Header-" + std::to_string(i); ++ std::string value = "value" + std::to_string(i); ++ headers.emplace(name, value); ++ } ++ ++ // This should work fine as we're under the limit ++ auto res = cli_.Get("/hi", headers); ++ EXPECT_TRUE(res); ++ if (res) { ++ EXPECT_EQ(StatusCode::OK_200, res->status); ++ } ++} ++ ++TEST_F(ServerTest, HeaderCountExceedsLimit) { ++ // Test with many headers to exceed the 100 limit ++ httplib::Headers headers; ++ ++ // Add 150 headers to definitely exceed the 100 limit ++ for (int i = 0; i < 150; i++) { ++ std::string name = "X-Test-Header-" + std::to_string(i); ++ std::string value = "value" + std::to_string(i); ++ headers.emplace(name, value); ++ } ++ ++ // This should fail due to exceeding header count limit ++ auto res = cli_.Get("/hi", headers); ++ ++ // The request should either fail or return 400 Bad Request ++ if (res) { ++ // If we get a response, it should be 400 Bad Request ++ EXPECT_EQ(StatusCode::BadRequest_400, res->status); ++ } else { ++ // Or the request should fail entirely ++ EXPECT_FALSE(res); ++ } ++} ++ + TEST_F(ServerTest, PercentEncoding) { + auto res = cli_.Get("/e%6edwith%"); + ASSERT_TRUE(res); +@@ -3717,6 +3765,32 @@ TEST_F(ServerTest, PlusSignEncoding) { + EXPECT_EQ("a +b", res->body); + } + ++TEST_F(ServerTest, HeaderCountSecurityTest) { ++ // This test simulates a potential DoS attack using many headers ++ // to verify our security fix prevents memory exhaustion ++ ++ httplib::Headers attack_headers; ++ ++ // Attempt to add many headers like an attacker would (200 headers to far exceed limit) ++ for (int i = 0; i < 200; i++) { ++ std::string name = "X-Attack-Header-" + std::to_string(i); ++ std::string value = "attack_payload_" + std::to_string(i); ++ attack_headers.emplace(name, value); ++ } ++ ++ // Try to POST with excessive headers ++ auto res = cli_.Post("/", attack_headers, "test_data", "text/plain"); ++ ++ // Should either fail or return 400 Bad Request due to security limit ++ if (res) { ++ // If we get a response, it should be 400 Bad Request ++ EXPECT_EQ(StatusCode::BadRequest_400, res->status); ++ } else { ++ // Request failed, which is the expected behavior for DoS protection ++ EXPECT_FALSE(res); ++ } ++} ++ + TEST_F(ServerTest, MultipartFormData) { + MultipartFormDataItems items = { + {"text1", "text default", "", ""}, diff -Nru cpp-httplib-0.18.7/debian/patches/0003-httplib.h-fix-CVE-2025-53629-Unbounded-Memory-Alloca.patch cpp-httplib-0.18.7/debian/patches/0003-httplib.h-fix-CVE-2025-53629-Unbounded-Memory-Alloca.patch --- cpp-httplib-0.18.7/debian/patches/0003-httplib.h-fix-CVE-2025-53629-Unbounded-Memory-Alloca.patch 1970-01-01 01:00:00.000000000 +0100 +++ cpp-httplib-0.18.7/debian/patches/0003-httplib.h-fix-CVE-2025-53629-Unbounded-Memory-Alloca.patch 2025-08-04 13:04:36.000000000 +0200 @@ -0,0 +1,450 @@ +From: yhirose <yhirose@users.noreply.github.com> +Date: Tue, 8 Jul 2025 17:11:13 -0400 +Subject: httplib.h: fix CVE-2025-53629 (Unbounded Memory Allocation in + Chunked/No-Length Requests) + +This patch complements the fix for CVE-2025-46728, actually solving +memory exhaustion attacks via chucked HTTP requests. + +Origin: backport, https://github.com/yhirose/cpp-httplib/commit/082acacd4581d10e05fccbe9cb336aa7822c4ea2 +Bug-Debian: https://bugs.debian.org/1109340 +Bug: https://github.com/yhirose/cpp-httplib/security/advisories/GHSA-qjmq-h3cc-qv6w +Forwarded: not-needed +Reviewed-By: Andrea Pappacoda <tachi@debian.org> +--- + httplib.h | 95 ++++++++++++++++------ + test/test.cc | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 327 insertions(+), 23 deletions(-) + +diff --git a/httplib.h b/httplib.h +index 0a5c6fc..bc887ea 100644 +--- a/httplib.h ++++ b/httplib.h +@@ -4327,51 +4327,79 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) { + } + } + +-inline bool read_content_without_length(Stream &strm, +- ContentReceiverWithProgress out) { ++enum class ReadContentResult { ++ Success, // Successfully read the content ++ PayloadTooLarge, // The content exceeds the specified payload limit ++ Error // An error occurred while reading the content ++}; ++ ++inline ReadContentResult ++read_content_without_length(Stream &strm, size_t payload_max_length, ++ ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); +- if (n <= 0) { return true; } ++ if (n == 0) { return ReadContentResult::Success; } ++ if (n < 0) { return ReadContentResult::Error; } ++ ++ // Check if adding this data would exceed the payload limit ++ if (r > payload_max_length || ++ payload_max_length - r < static_cast<uint64_t>(n)) { ++ return ReadContentResult::PayloadTooLarge; ++ } + +- if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; } ++ if (!out(buf, static_cast<size_t>(n), r, 0)) { ++ return ReadContentResult::Error; ++ } + r += static_cast<uint64_t>(n); + } + +- return true; ++ return ReadContentResult::Success; + } + + template <typename T> +-inline bool read_content_chunked(Stream &strm, T &x, +- ContentReceiverWithProgress out) { ++inline ReadContentResult read_content_chunked(Stream &strm, T &x, ++ size_t payload_max_length, ++ ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + +- if (!line_reader.getline()) { return false; } ++ if (!line_reader.getline()) { return ReadContentResult::Error; } + + unsigned long chunk_len; ++ uint64_t total_len = 0; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + +- if (end_ptr == line_reader.ptr()) { return false; } +- if (chunk_len == ULONG_MAX) { return false; } ++ if (end_ptr == line_reader.ptr()) { return ReadContentResult::Error; } ++ if (chunk_len == ULONG_MAX) { return ReadContentResult::Error; } + + if (chunk_len == 0) { break; } + ++ // Check if adding this chunk would exceed the payload limit ++ if (total_len > payload_max_length || ++ payload_max_length - total_len < chunk_len) { ++ return ReadContentResult::PayloadTooLarge; ++ } ++ ++ total_len += chunk_len; ++ + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { +- return false; ++ return ReadContentResult::Error; + } + +- if (!line_reader.getline()) { return false; } ++ if (!line_reader.getline()) { return ReadContentResult::Error; } + +- if (strcmp(line_reader.ptr(), "\r\n") != 0) { return false; } ++ if (strcmp(line_reader.ptr(), "\r\n") != 0) { ++ return ReadContentResult::Error; ++ } + +- if (!line_reader.getline()) { return false; } ++ if (!line_reader.getline()) { return ReadContentResult::Error; } + } + + assert(chunk_len == 0); +@@ -4386,16 +4414,20 @@ inline bool read_content_chunked(Stream &strm, T &x, + // to be ok whether the final CRLF exists or not in the chunked data. + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3 + // +- // According to the reference code in RFC 9112, cpp-htpplib now allows +- // chuncked transfer coding data without the final CRLF. +- if (!line_reader.getline()) { return true; } ++ // According to the reference code in RFC 9112, cpp-httplib now allows ++ // chunked transfer coding data without the final CRLF. ++ if (!line_reader.getline()) { return ReadContentResult::Success; } + + size_t trailer_header_count = 0; + while (strcmp(line_reader.ptr(), "\r\n") != 0) { +- if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } ++ if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { ++ return ReadContentResult::Error; ++ } + + // Check trailer header count limit +- if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; } ++ if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { ++ return ReadContentResult::Error; ++ } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; +@@ -4408,10 +4440,10 @@ inline bool read_content_chunked(Stream &strm, T &x, + + trailer_header_count++; + +- if (!line_reader.getline()) { return false; } ++ if (!line_reader.getline()) { return ReadContentResult::Error; } + } + +- return true; ++ return ReadContentResult::Success; + } + + inline bool is_chunked_transfer_encoding(const Headers &headers) { +@@ -4478,9 +4510,26 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { +- ret = read_content_chunked(strm, x, out); ++ auto result = read_content_chunked(strm, x, payload_max_length, out); ++ if (result == ReadContentResult::Success) { ++ ret = true; ++ } else if (result == ReadContentResult::PayloadTooLarge) { ++ exceed_payload_max_length = true; ++ ret = false; ++ } else { ++ ret = false; ++ } + } else if (!has_header(x.headers, "Content-Length")) { +- ret = read_content_without_length(strm, out); ++ auto result = ++ read_content_without_length(strm, payload_max_length, out); ++ if (result == ReadContentResult::Success) { ++ ret = true; ++ } else if (result == ReadContentResult::PayloadTooLarge) { ++ exceed_payload_max_length = true; ++ ret = false; ++ } else { ++ ret = false; ++ } + } else { + auto is_invalid_value = false; + auto len = get_header_value_u64( +diff --git a/test/test.cc b/test/test.cc +index e29be7d..4d4a742 100644 +--- a/test/test.cc ++++ b/test/test.cc +@@ -6134,6 +6134,261 @@ TEST_F(PayloadMaxLengthTest, ExceedLimit) { + EXPECT_EQ(StatusCode::OK_200, res->status); + } + ++TEST_F(PayloadMaxLengthTest, ChunkedEncodingSecurityTest) { ++ // Test chunked encoding with payload exceeding the 8-byte limit ++ std::string large_chunked_data(16, 'A'); // 16 bytes, exceeds 8-byte limit ++ ++ auto res = cli_.Post("/test", large_chunked_data, "text/plain"); ++ ASSERT_TRUE(res); ++ EXPECT_EQ(StatusCode::PayloadTooLarge_413, res->status); ++} ++ ++TEST_F(PayloadMaxLengthTest, ChunkedEncodingWithinLimit) { ++ // Test chunked encoding with payload within the 8-byte limit ++ std::string small_chunked_data(4, 'B'); // 4 bytes, within 8-byte limit ++ ++ auto res = cli_.Post("/test", small_chunked_data, "text/plain"); ++ ASSERT_TRUE(res); ++ EXPECT_EQ(StatusCode::OK_200, res->status); ++} ++ ++TEST_F(PayloadMaxLengthTest, RawSocketChunkedTest) { ++ // Test using send_request to send chunked data exceeding payload limit ++ std::string chunked_request = "POST /test HTTP/1.1\r\n" ++ "Host: " + ++ std::string(HOST) + ":" + std::to_string(PORT) + ++ "\r\n" ++ "Transfer-Encoding: chunked\r\n" ++ "Connection: close\r\n" ++ "\r\n" ++ "a\r\n" // 10 bytes chunk (exceeds 8-byte limit) ++ "0123456789\r\n" ++ "0\r\n" // End chunk ++ "\r\n"; ++ ++ std::string response; ++ bool result = send_request(1, chunked_request, &response); ++ ++ if (!result) { ++ // If send_request fails, it might be because the server closed the ++ // connection due to payload limit enforcement, which is acceptable ++ SUCCEED() ++ << "Server rejected oversized chunked request (connection closed)"; ++ } else { ++ // If we got a response, check if it's an error response or connection was ++ // closed early Short response length indicates connection was closed due to ++ // payload limit ++ if (response.length() <= 10) { ++ SUCCEED() << "Server closed connection for oversized chunked request"; ++ } else { ++ // Check for error status codes ++ EXPECT_TRUE(response.find("413") != std::string::npos || ++ response.find("Payload Too Large") != std::string::npos || ++ response.find("400") != std::string::npos); ++ } ++ } ++} ++ ++TEST_F(PayloadMaxLengthTest, NoContentLengthPayloadLimit) { ++ // Test request without Content-Length header exceeding payload limit ++ std::string request_without_content_length = "POST /test HTTP/1.1\r\n" ++ "Host: " + ++ std::string(HOST) + ":" + ++ std::to_string(PORT) + ++ "\r\n" ++ "Connection: close\r\n" ++ "\r\n"; ++ ++ // Add payload exceeding the 8-byte limit ++ std::string large_payload(16, 'X'); // 16 bytes, exceeds 8-byte limit ++ request_without_content_length += large_payload; ++ ++ std::string response; ++ bool result = send_request(1, request_without_content_length, &response); ++ ++ if (!result) { ++ // If send_request fails, server likely closed connection due to payload ++ // limit ++ SUCCEED() << "Server rejected oversized request without Content-Length " ++ "(connection closed)"; ++ } else { ++ // Check if server responded with error or closed connection early ++ if (response.length() <= 10) { ++ SUCCEED() << "Server closed connection for oversized request without " ++ "Content-Length"; ++ } else { ++ // Check for error status codes ++ EXPECT_TRUE(response.find("413") != std::string::npos || ++ response.find("Payload Too Large") != std::string::npos || ++ response.find("400") != std::string::npos); ++ } ++ } ++} ++ ++TEST_F(PayloadMaxLengthTest, NoContentLengthWithinLimit) { ++ // Test request without Content-Length header within payload limit ++ std::string request_without_content_length = "POST /test HTTP/1.1\r\n" ++ "Host: " + ++ std::string(HOST) + ":" + ++ std::to_string(PORT) + ++ "\r\n" ++ "Connection: close\r\n" ++ "\r\n"; ++ ++ // Add payload within the 8-byte limit ++ std::string small_payload(4, 'Y'); // 4 bytes, within 8-byte limit ++ request_without_content_length += small_payload; ++ ++ std::string response; ++ bool result = send_request(1, request_without_content_length, &response); ++ ++ // For requests without Content-Length, the server may have different behavior ++ // The key is that it should not reject due to payload limit for small ++ // payloads ++ if (result) { ++ // Check for any HTTP response (success or error, but not connection closed) ++ if (response.length() > 10) { ++ SUCCEED() ++ << "Server processed request without Content-Length within limit"; ++ } else { ++ // Short response might indicate connection closed, which is acceptable ++ SUCCEED() << "Server closed connection for request without " ++ "Content-Length (acceptable behavior)"; ++ } ++ } else { ++ // Connection failure might be due to protocol requirements ++ SUCCEED() << "Connection issue with request without Content-Length " ++ "(environment-specific)"; ++ } ++} ++ ++class LargePayloadMaxLengthTest : public ::testing::Test { ++protected: ++ LargePayloadMaxLengthTest() ++ : cli_(HOST, PORT) ++#ifdef CPPHTTPLIB_OPENSSL_SUPPORT ++ , ++ svr_(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE) ++#endif ++ { ++#ifdef CPPHTTPLIB_OPENSSL_SUPPORT ++ cli_.enable_server_certificate_verification(false); ++#endif ++ } ++ ++ virtual void SetUp() { ++ // Set 10MB payload limit ++ const size_t LARGE_PAYLOAD_LIMIT = 10 * 1024 * 1024; // 10MB ++ svr_.set_payload_max_length(LARGE_PAYLOAD_LIMIT); ++ ++ svr_.Post("/test", [&](const Request & /*req*/, Response &res) { ++ res.set_content("Large payload test", "text/plain"); ++ }); ++ ++ t_ = thread([&]() { ASSERT_TRUE(svr_.listen(HOST, PORT)); }); ++ svr_.wait_until_ready(); ++ } ++ ++ virtual void TearDown() { ++ svr_.stop(); ++ t_.join(); ++ } ++ ++#ifdef CPPHTTPLIB_OPENSSL_SUPPORT ++ SSLClient cli_; ++ SSLServer svr_; ++#else ++ Client cli_; ++ Server svr_; ++#endif ++ thread t_; ++}; ++ ++TEST_F(LargePayloadMaxLengthTest, ChunkedEncodingWithin10MB) { ++ // Test chunked encoding with payload within 10MB limit ++ std::string medium_payload(5 * 1024 * 1024, ++ 'A'); // 5MB payload, within 10MB limit ++ ++ auto res = cli_.Post("/test", medium_payload, "application/octet-stream"); ++ ASSERT_TRUE(res); ++ EXPECT_EQ(StatusCode::OK_200, res->status); ++} ++ ++TEST_F(LargePayloadMaxLengthTest, ChunkedEncodingExceeds10MB) { ++ // Test chunked encoding with payload exceeding 10MB limit ++ std::string large_payload(12 * 1024 * 1024, ++ 'B'); // 12MB payload, exceeds 10MB limit ++ ++ auto res = cli_.Post("/test", large_payload, "application/octet-stream"); ++ ASSERT_TRUE(res); ++ EXPECT_EQ(StatusCode::PayloadTooLarge_413, res->status); ++} ++ ++TEST_F(LargePayloadMaxLengthTest, NoContentLengthWithin10MB) { ++ // Test request without Content-Length header within 10MB limit ++ std::string request_without_content_length = "POST /test HTTP/1.1\r\n" ++ "Host: " + ++ std::string(HOST) + ":" + ++ std::to_string(PORT) + ++ "\r\n" ++ "Connection: close\r\n" ++ "\r\n"; ++ ++ // Add 1MB payload (within 10MB limit) ++ std::string medium_payload(1024 * 1024, 'C'); // 1MB payload ++ request_without_content_length += medium_payload; ++ ++ std::string response; ++ bool result = send_request(5, request_without_content_length, &response); ++ ++ if (result) { ++ // Should get a proper HTTP response for payloads within limit ++ if (response.length() > 10) { ++ SUCCEED() << "Server processed 1MB request without Content-Length within " ++ "10MB limit"; ++ } else { ++ SUCCEED() << "Server closed connection (acceptable behavior for no " ++ "Content-Length)"; ++ } ++ } else { ++ SUCCEED() << "Connection issue with 1MB payload (environment-specific)"; ++ } ++} ++ ++TEST_F(LargePayloadMaxLengthTest, NoContentLengthExceeds10MB) { ++ // Test request without Content-Length header exceeding 10MB limit ++ std::string request_without_content_length = "POST /test HTTP/1.1\r\n" ++ "Host: " + ++ std::string(HOST) + ":" + ++ std::to_string(PORT) + ++ "\r\n" ++ "Connection: close\r\n" ++ "\r\n"; ++ ++ // Add 12MB payload (exceeds 10MB limit) ++ std::string large_payload(12 * 1024 * 1024, 'D'); // 12MB payload ++ request_without_content_length += large_payload; ++ ++ std::string response; ++ bool result = send_request(10, request_without_content_length, &response); ++ ++ if (!result) { ++ // Server should close connection due to payload limit ++ SUCCEED() << "Server rejected 12MB request without Content-Length " ++ "(connection closed)"; ++ } else { ++ // Check for error response ++ if (response.length() <= 10) { ++ SUCCEED() ++ << "Server closed connection for 12MB request exceeding 10MB limit"; ++ } else { ++ EXPECT_TRUE(response.find("413") != std::string::npos || ++ response.find("Payload Too Large") != std::string::npos || ++ response.find("400") != std::string::npos); ++ } ++ } ++} ++ + TEST(HostAndPortPropertiesTest, NoSSL) { + httplib::Client cli("www.google.com", 1234); + ASSERT_EQ("www.google.com", cli.host()); diff -Nru cpp-httplib-0.18.7/debian/patches/series cpp-httplib-0.18.7/debian/patches/series --- cpp-httplib-0.18.7/debian/patches/series 1970-01-01 01:00:00.000000000 +0100 +++ cpp-httplib-0.18.7/debian/patches/series 2025-08-04 13:04:36.000000000 +0200 @@ -0,0 +1,3 @@ +0001-httplib.h-fix-CVE-2025-46728-DoS-via-unbounded-reque.patch +0002-httplib.h-fix-CVE-2025-52887-Unlimited-number-of-htt.patch +0003-httplib.h-fix-CVE-2025-53629-Unbounded-Memory-Alloca.patchAttachment: signature.asc
Description: PGP signature
--- End Message ---
--- Begin Message ---
- To: Andrea Pappacoda <tachi@debian.org>, 1110393-done@bugs.debian.org
- Subject: Re: Bug#1110393: unblock: cpp-httplib/0.18.7-2
- From: Paul Gevers <elbrus@debian.org>
- Date: Mon, 4 Aug 2025 15:17:15 +0200
- Message-id: <3eed0c14-5a46-41b8-bd53-6fa3aa5302e8@debian.org>
- In-reply-to: <[🔎] DBTMOV7710AS.3PO0EYZ9FC8XH@debian.org>
- References: <[🔎] DBTMOV7710AS.3PO0EYZ9FC8XH@debian.org>
Hi, On 04-08-2025 14:05, Andrea Pappacoda wrote:I have cherry picked and backported fixes for three CVEs, one of which is for an easy to perform DoS attack for the server component of cpp- httplib.We're in the quite week before the release [1]. Please arrange security updates with the security team.Paul [1] https://lists.debian.org/debian-devel-announce/2025/07/msg00003.htmlAttachment: OpenPGP_signature.asc
Description: OpenPGP digital signature
--- End Message ---