File CVE-2025-52887.patch of Package cpp-httplib.openSUSE_Backports_SLE-15-SP6_Update
From 28dcf379e82a2cdb544d812696a7fd46067eb7f9 Mon Sep 17 00:00:00 2001
From: yhirose <yhirose@users.noreply.github.com>
Date: Tue, 24 Jun 2025 07:56:00 -0400
Subject: [PATCH] Merge commit from fork
---
httplib.h | 17 ++++++++++++
test/test.cc | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 91 insertions(+)
Index: cpp-httplib-0.20.1/httplib.h
===================================================================
--- cpp-httplib-0.20.1.orig/httplib.h
+++ cpp-httplib-0.20.1/httplib.h
@@ -90,6 +90,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
@@ -4334,6 +4338,8 @@ inline bool read_headers(Stream &strm, H
char buf[bufsiz];
stream_line_reader line_reader(strm, buf, bufsiz);
+ size_t header_count = 0;
+
for (;;) {
if (!line_reader.getline()) { return false; }
@@ -4354,6 +4360,9 @@ inline bool read_headers(Stream &strm, H
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;
@@ -4363,6 +4372,8 @@ inline bool read_headers(Stream &strm, H
})) {
return false;
}
+
+ header_count++;
}
return true;
@@ -4465,9 +4476,13 @@ inline bool read_content_chunked(Stream
// chunked 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;
@@ -4477,6 +4492,8 @@ inline bool read_content_chunked(Stream
x.headers.emplace(key, val);
});
+ trailer_header_count++;
+
if (!line_reader.getline()) { return false; }
}
Index: cpp-httplib-0.20.1/test/test.cc
===================================================================
--- cpp-httplib-0.20.1.orig/test/test.cc
+++ cpp-httplib-0.20.1/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>
@@ -3725,6 +3729,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);
@@ -3762,6 +3810,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", "", ""},