Merge commit from fork

* Ensure payload_max_length_ is respected for compressed payloads

* Fix Denial of service (DOS) using zip bomb

---------

Co-authored-by: Hritik Vijay <hey@hritik.sh>
This commit is contained in:
yhirose
2026-01-09 21:09:07 -05:00
committed by GitHub
parent 59905c7f0d
commit 2e2e47bab1
2 changed files with 54 additions and 8 deletions

View File

@@ -8982,14 +8982,11 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
strm, req, res,
// Regular
[&](const char *buf, size_t n) {
// Prevent arithmetic overflow when checking sizes.
// Avoid computing (req.body.size() + n) directly because
// adding two unsigned `size_t` values can wrap around and
// produce a small result instead of indicating overflow.
// Instead, check using subtraction: ensure `n` does not
// exceed the remaining capacity `max_size() - size()`.
if (req.body.size() >= req.body.max_size() ||
n > req.body.max_size() - req.body.size()) {
// Limit decompressed body size to payload_max_length_ to protect
// against "zip bomb" attacks where a small compressed payload
// decompresses to a massive size.
if (req.body.size() + n > payload_max_length_ ||
req.body.size() + n > req.body.max_size()) {
return false;
}
req.body.append(buf, n);

View File

@@ -14101,3 +14101,52 @@ TEST(Issue2318Test, EmptyHostString) {
EXPECT_EQ(httplib::Error::Connection, res.error());
}
}
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
TEST(ZipBombProtectionTest, DecompressedSizeExceedsLimit) {
Server svr;
// Set a small payload limit (1KB)
svr.set_payload_max_length(1024);
svr.Post("/test", [&](const Request &req, Response &res) {
res.set_content("Body size: " + std::to_string(req.body.size()),
"text/plain");
});
auto listen_thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
listen_thread.join();
});
svr.wait_until_ready();
// Create data that compresses well but exceeds limit when decompressed
// 8KB of repeated null bytes compresses to a very small size
std::string original_data(8 * 1024, '\0');
// Compress the data using gzip
std::string compressed_data;
detail::gzip_compressor compressor;
compressor.compress(original_data.data(), original_data.size(), true,
[&](const char *data, size_t size) {
compressed_data.append(data, size);
return true;
});
// Verify compression worked (compressed should be much smaller)
ASSERT_LT(compressed_data.size(), original_data.size());
ASSERT_LT(compressed_data.size(), 1024u); // Compressed fits in limit
// Send compressed data with Content-Encoding: gzip
Client cli(HOST, PORT);
Headers headers = {{"Content-Encoding", "gzip"}};
auto res =
cli.Post("/test", headers, compressed_data, "application/octet-stream");
// Server should reject because decompressed size (8KB) exceeds limit (1KB)
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
}
#endif