From a1ba73a2564b452c8a81491d781bf3ba15c1f918 Mon Sep 17 00:00:00 2001 From: Wei-Lun Li Date: Thu, 5 Nov 2026 08:06:30 +0900 Subject: [PATCH] Cap a send() or wolfSSL_read() length at INT_MAX (#2655) * Cap a send() or wolfSSL_read() length at INT_MAX On 64-bit Windows SocketStream::write() handed size to send(), whose length is an int, so a single write of 4 GiB or more went out as its low 32 bits: a 4 GiB + 1 byte write sent one byte, and write_data() then retried the remaining 4 GiB as zero-byte sends forever. The INT_MAX cap that read() has was limited to 32-bit Windows in df20c27 (#1294), when the body was still written with one unchecked write(); write_data() now sends the rest, so the cap applies to every Windows build again. The wolfSSL tls::read() had the same cast, so a read into a buffer over INT_MAX failed. Cap it as the OpenSSL backend has done since f80864c. * Drop the 4 GiB write test and shorten comments The Windows-only test allocated 4 GiB and took about 5 s, which is too heavy for CI. --------- Co-authored-by: yhirose --- httplib.h | 6 +++++- test/test.cc | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/httplib.h b/httplib.h index ac49c1cd..c1a9366e 100644 --- a/httplib.h +++ b/httplib.h @@ -12876,7 +12876,8 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) { inline ssize_t SocketStream::write(const char *ptr, size_t size) { if (!wait_writable()) { return -1; } -#if defined(_WIN32) && !defined(_WIN64) + // Winsock's send() takes an int length, even on 64-bit Windows +#ifdef _WIN32 size = (std::min)(size, static_cast((std::numeric_limits::max)())); #endif @@ -23054,6 +23055,9 @@ inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) { } auto wsession = static_cast(session); + constexpr auto max_len = + static_cast((std::numeric_limits::max)()); + if (len > max_len) { len = max_len; } int ret = wolfSSL_read(wsession->ssl, buf, static_cast(len)); if (ret > 0) { diff --git a/test/test.cc b/test/test.cc index e6b893e7..317461c1 100644 --- a/test/test.cc +++ b/test/test.cc @@ -25056,6 +25056,49 @@ TEST(SSLTest, ResponseBodyTerminatedByConnectionClose) { EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(expected_body, res->body); } + +// Without Content-Length, the full buffer size reaches tls::read(). +TEST(SSLTest, ReadIntoBufferOverIntMax) { + if (sizeof(size_t) <= sizeof(int)) { + GTEST_SKIP() << "size_t cannot exceed INT_MAX here"; + } + + SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE); + ASSERT_TRUE(svr.is_valid()); + + svr.Get("/no-content-length", [&](const Request & /*req*/, Response &res) { + res.set_content_provider("text/plain", + [&](size_t offset, DataSink &sink) -> bool { + if (offset == 0) { sink.write("Hello", 5); } + sink.done(); + return true; + }); + }); + + auto port = svr.bind_to_any_port(HOST); + auto listen_thread = std::thread([&svr] { svr.listen_after_bind(); }); + auto se = detail::scope_exit([&] { + svr.stop(); + listen_thread.join(); + }); + svr.wait_until_ready(); + + SSLClient cli(HOST, port); + cli.enable_server_certificate_verification(false); + + auto handle = cli.open_stream("GET", "/no-content-length"); + ASSERT_TRUE(handle.is_valid()) << to_string(handle.error); + + const auto len = static_cast((std::numeric_limits::max)()) + 1; + std::unique_ptr buf(new char[len]); + std::string body; + ssize_t n; + while ((n = handle.read(buf.get(), len)) > 0) { + body.append(buf.get(), static_cast(n)); + } + EXPECT_EQ(0, n); + EXPECT_EQ("Hello", body); +} #endif // CPPHTTPLIB_SSL_ENABLED //==============================================================================