Ver Fonte

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 <yuji.hirose.bug@gmail.com>
Wei-Lun Li há 3 dias atrás
pai
commit
a1ba73a256
2 ficheiros alterados com 48 adições e 1 exclusões
  1. 5 1
      httplib.h
  2. 43 0
      test/test.cc

+ 5 - 1
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<size_t>((std::numeric_limits<int>::max)()));
 #endif
@@ -23054,6 +23055,9 @@ inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
   }
 
   auto wsession = static_cast<impl::WolfSSLSession *>(session);
+  constexpr auto max_len =
+      static_cast<size_t>((std::numeric_limits<int>::max)());
+  if (len > max_len) { len = max_len; }
   int ret = wolfSSL_read(wsession->ssl, buf, static_cast<int>(len));
 
   if (ret > 0) {

+ 43 - 0
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<size_t>((std::numeric_limits<int>::max)()) + 1;
+  std::unique_ptr<char[]> buf(new char[len]);
+  std::string body;
+  ssize_t n;
+  while ((n = handle.read(buf.get(), len)) > 0) {
+    body.append(buf.get(), static_cast<size_t>(n));
+  }
+  EXPECT_EQ(0, n);
+  EXPECT_EQ("Hello", body);
+}
 #endif // CPPHTTPLIB_SSL_ENABLED
 
 //==============================================================================