1
0
mirror of synced 2025-07-17 17:40:57 +03:00

Merge commit from fork

* Fix Persistency of Unbounded Memory Allocation in Chunked/No-Length Requests Vulnerability

* Revert HTTP status code from 413 to 400
This commit is contained in:
yhirose
2025-07-08 17:11:13 -04:00
committed by GitHub
parent 52163ed982
commit 082acacd45
2 changed files with 326 additions and 23 deletions

View File

@ -4642,52 +4642,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 false; }
if (n == 0) { return ReadContentResult::Success; }
if (n < 0) { return ReadContentResult::Error; }
if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
// 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 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; }
if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
return false;
// 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;
}
if (!line_reader.getline()) { return false; }
total_len += chunk_len;
if (strcmp(line_reader.ptr(), "\r\n") != 0) { return false; }
if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
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 ReadContentResult::Error;
}
if (!line_reader.getline()) { return ReadContentResult::Error; }
}
assert(chunk_len == 0);
@ -4704,14 +4731,18 @@ inline bool read_content_chunked(Stream &strm, T &x,
//
// 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 true; }
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;
@ -4724,10 +4755,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) {
@ -4801,9 +4832,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(