diff --git a/http-client.carp b/http-client.carp index b1e4fdd..3f3fe61 100644 --- a/http-client.carp +++ b/http-client.carp @@ -154,10 +154,8 @@ from the streams library. (set-buf! s (String.suffix &raw consumed)) (set-decoded! s @"") (set! result (Maybe.Just out))) - (= rc -1) - ; end of stream - (do (set-done! s true) (set! result (Maybe.Nothing)) (break)) - ; rc == 0: need more data + (= rc 0) + ; need more data (match (Connection.read (conn s)) (Result.Success chunk) (if (= (String.length &chunk) 0) @@ -167,7 +165,9 @@ from the streams library. (StringBuf.append-str &sb (buf s)) (StringBuf.append-str &sb &chunk) (set-buf! s (StringBuf.to-string &sb)))) - (Result.Error _) (do (set-done! s true) (break))))))) + (Result.Error _) (do (set-done! s true) (break))) + ; rc < 0: end of stream (-1) or parse error (-2) + (do (set-done! s true) (set! result (Maybe.Nothing)) (break)))))) (StringBuf.delete sb) result)) diff --git a/src/chunked.h b/src/chunked.h index 34145d5..05e683d 100644 --- a/src/chunked.h +++ b/src/chunked.h @@ -3,6 +3,12 @@ #include #include +#include +#include + +/* Maximum chunk size: 16 MiB. Prevents malicious servers from triggering + * unbounded allocations via an enormous chunk-size line. */ +#define CHUNKED_MAX_CHUNK_SIZE (16 * 1024 * 1024) /* Decode one chunk from a chunked transfer-encoding buffer. * @@ -13,6 +19,7 @@ * 1 = decoded a chunk (data in *out, length in *consumed) * 0 = need more data (incomplete chunk in buffer) * -1 = end of stream (chunk size 0) + * -2 = parse error (invalid hex, overflow, or chunk too large) */ int chunked_decode_one(const char *buf, int buf_len, String *out, int *consumed) { /* Find \r\n to read chunk size */ @@ -25,8 +32,19 @@ int chunked_decode_one(const char *buf, int buf_len, String *out, int *consumed) } if (!crlf) return 0; /* need more data */ - /* Parse hex chunk size */ - long chunk_size = strtol(buf, NULL, 16); + /* The chunk-size line must start with a hex digit (RFC 7230 ยง4.1). */ + if (buf == crlf || !isxdigit((unsigned char)buf[0])) return -2; + + /* Parse hex chunk size, validating that strtol consumed meaningful input + * and stopped at an expected delimiter (\r for end-of-size, or ; for a + * chunk extension per RFC 7230). */ + char *endptr = NULL; + long chunk_size = strtol(buf, &endptr, 16); + if (endptr == buf || (endptr != (char *)crlf && *endptr != ';')) return -2; + + /* Reject negative values (sign prefix) and sizes above the safety cap. */ + if (chunk_size < 0 || chunk_size > CHUNKED_MAX_CHUNK_SIZE) return -2; + if (chunk_size == 0) { *consumed = (int)(crlf - buf) + 2; /* skip "0\r\n" */ *out = CARP_MALLOC(1); @@ -35,7 +53,11 @@ int chunked_decode_one(const char *buf, int buf_len, String *out, int *consumed) } int header_len = (int)(crlf - buf) + 2; /* "1a\r\n" */ + + /* Guard against int overflow in the total-needed calculation. */ + if (chunk_size > INT_MAX - header_len - 2) return -2; int needed = header_len + (int)chunk_size + 2; /* +2 for trailing \r\n */ + if (buf_len < needed) return 0; /* need more data */ /* Extract chunk data */