Skip to content

Commit 082acac

Browse files
authored
Merge commit from fork
* Fix Persistency of Unbounded Memory Allocation in Chunked/No-Length Requests Vulnerability * Revert HTTP status code from 413 to 400
1 parent 52163ed commit 082acac

File tree

2 files changed

+325
-22
lines changed

2 files changed

+325
-22
lines changed

httplib.h

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4642,52 +4642,79 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) {
46424642
}
46434643
}
46444644

4645-
inline bool read_content_without_length(Stream &strm,
4646-
ContentReceiverWithProgress out) {
4645+
enum class ReadContentResult {
4646+
Success, // Successfully read the content
4647+
PayloadTooLarge, // The content exceeds the specified payload limit
4648+
Error // An error occurred while reading the content
4649+
};
4650+
4651+
inline ReadContentResult
4652+
read_content_without_length(Stream &strm, size_t payload_max_length,
4653+
ContentReceiverWithProgress out) {
46474654
char buf[CPPHTTPLIB_RECV_BUFSIZ];
46484655
uint64_t r = 0;
46494656
for (;;) {
46504657
auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
4651-
if (n == 0) { return true; }
4652-
if (n < 0) { return false; }
4658+
if (n == 0) { return ReadContentResult::Success; }
4659+
if (n < 0) { return ReadContentResult::Error; }
4660+
4661+
// Check if adding this data would exceed the payload limit
4662+
if (r > payload_max_length ||
4663+
payload_max_length - r < static_cast<uint64_t>(n)) {
4664+
return ReadContentResult::PayloadTooLarge;
4665+
}
46534666

4654-
if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
4667+
if (!out(buf, static_cast<size_t>(n), r, 0)) {
4668+
return ReadContentResult::Error;
4669+
}
46554670
r += static_cast<uint64_t>(n);
46564671
}
46574672

4658-
return true;
4673+
return ReadContentResult::Success;
46594674
}
46604675

46614676
template <typename T>
4662-
inline bool read_content_chunked(Stream &strm, T &x,
4663-
ContentReceiverWithProgress out) {
4677+
inline ReadContentResult read_content_chunked(Stream &strm, T &x,
4678+
size_t payload_max_length,
4679+
ContentReceiverWithProgress out) {
46644680
const auto bufsiz = 16;
46654681
char buf[bufsiz];
46664682

46674683
stream_line_reader line_reader(strm, buf, bufsiz);
46684684

4669-
if (!line_reader.getline()) { return false; }
4685+
if (!line_reader.getline()) { return ReadContentResult::Error; }
46704686

46714687
unsigned long chunk_len;
4688+
uint64_t total_len = 0;
46724689
while (true) {
46734690
char *end_ptr;
46744691

46754692
chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16);
46764693

4677-
if (end_ptr == line_reader.ptr()) { return false; }
4678-
if (chunk_len == ULONG_MAX) { return false; }
4694+
if (end_ptr == line_reader.ptr()) { return ReadContentResult::Error; }
4695+
if (chunk_len == ULONG_MAX) { return ReadContentResult::Error; }
46794696

46804697
if (chunk_len == 0) { break; }
46814698

4699+
// Check if adding this chunk would exceed the payload limit
4700+
if (total_len > payload_max_length ||
4701+
payload_max_length - total_len < chunk_len) {
4702+
return ReadContentResult::PayloadTooLarge;
4703+
}
4704+
4705+
total_len += chunk_len;
4706+
46824707
if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
4683-
return false;
4708+
return ReadContentResult::Error;
46844709
}
46854710

4686-
if (!line_reader.getline()) { return false; }
4711+
if (!line_reader.getline()) { return ReadContentResult::Error; }
46874712

4688-
if (strcmp(line_reader.ptr(), "\r\n") != 0) { return false; }
4713+
if (strcmp(line_reader.ptr(), "\r\n") != 0) {
4714+
return ReadContentResult::Error;
4715+
}
46894716

4690-
if (!line_reader.getline()) { return false; }
4717+
if (!line_reader.getline()) { return ReadContentResult::Error; }
46914718
}
46924719

46934720
assert(chunk_len == 0);
@@ -4704,14 +4731,18 @@ inline bool read_content_chunked(Stream &strm, T &x,
47044731
//
47054732
// According to the reference code in RFC 9112, cpp-httplib now allows
47064733
// chunked transfer coding data without the final CRLF.
4707-
if (!line_reader.getline()) { return true; }
4734+
if (!line_reader.getline()) { return ReadContentResult::Success; }
47084735

47094736
size_t trailer_header_count = 0;
47104737
while (strcmp(line_reader.ptr(), "\r\n") != 0) {
4711-
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
4738+
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) {
4739+
return ReadContentResult::Error;
4740+
}
47124741

47134742
// Check trailer header count limit
4714-
if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
4743+
if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) {
4744+
return ReadContentResult::Error;
4745+
}
47154746

47164747
// Exclude line terminator
47174748
constexpr auto line_terminator_len = 2;
@@ -4724,10 +4755,10 @@ inline bool read_content_chunked(Stream &strm, T &x,
47244755

47254756
trailer_header_count++;
47264757

4727-
if (!line_reader.getline()) { return false; }
4758+
if (!line_reader.getline()) { return ReadContentResult::Error; }
47284759
}
47294760

4730-
return true;
4761+
return ReadContentResult::Success;
47314762
}
47324763

47334764
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,
48014832
auto exceed_payload_max_length = false;
48024833

48034834
if (is_chunked_transfer_encoding(x.headers)) {
4804-
ret = read_content_chunked(strm, x, out);
4835+
auto result = read_content_chunked(strm, x, payload_max_length, out);
4836+
if (result == ReadContentResult::Success) {
4837+
ret = true;
4838+
} else if (result == ReadContentResult::PayloadTooLarge) {
4839+
exceed_payload_max_length = true;
4840+
ret = false;
4841+
} else {
4842+
ret = false;
4843+
}
48054844
} else if (!has_header(x.headers, "Content-Length")) {
4806-
ret = read_content_without_length(strm, out);
4845+
auto result =
4846+
read_content_without_length(strm, payload_max_length, out);
4847+
if (result == ReadContentResult::Success) {
4848+
ret = true;
4849+
} else if (result == ReadContentResult::PayloadTooLarge) {
4850+
exceed_payload_max_length = true;
4851+
ret = false;
4852+
} else {
4853+
ret = false;
4854+
}
48074855
} else {
48084856
auto is_invalid_value = false;
48094857
auto len = get_header_value_u64(

0 commit comments

Comments
 (0)