Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit efd9a98

Browse files
committedJul 7, 2021
fix(http1): protect against overflow in chunked decoder
The HTTP/1 chunked decoder, when decoding the size of a chunk, could overflow the size if the hex digits were too large. This fixes it by adding an overflow check in the decoder. See GHSA-5h46-h7hh-c6x9
1 parent 11cb472 commit efd9a98

File tree

2 files changed

+47
-7
lines changed

2 files changed

+47
-7
lines changed
 

‎src/proto/h1/decode.rs

+22-7
Original file line numberDiff line numberDiff line change
@@ -208,19 +208,32 @@ impl ChunkedState {
208208
size: &mut u64,
209209
) -> Poll<Result<ChunkedState, io::Error>> {
210210
trace!("Read chunk hex size");
211+
212+
macro_rules! or_overflow {
213+
($e:expr) => (
214+
match $e {
215+
Some(val) => val,
216+
None => return Poll::Ready(Err(io::Error::new(
217+
io::ErrorKind::InvalidData,
218+
"invalid chunk size: overflow",
219+
))),
220+
}
221+
)
222+
}
223+
211224
let radix = 16;
212225
match byte!(rdr, cx) {
213226
b @ b'0'..=b'9' => {
214-
*size *= radix;
215-
*size += (b - b'0') as u64;
227+
*size = or_overflow!(size.checked_mul(radix));
228+
*size = or_overflow!(size.checked_add((b - b'0') as u64));
216229
}
217230
b @ b'a'..=b'f' => {
218-
*size *= radix;
219-
*size += (b + 10 - b'a') as u64;
231+
*size = or_overflow!(size.checked_mul(radix));
232+
*size = or_overflow!(size.checked_add((b + 10 - b'a') as u64));
220233
}
221234
b @ b'A'..=b'F' => {
222-
*size *= radix;
223-
*size += (b + 10 - b'A') as u64;
235+
*size = or_overflow!(size.checked_mul(radix));
236+
*size = or_overflow!(size.checked_add((b + 10 - b'A') as u64));
224237
}
225238
b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)),
226239
b';' => return Poll::Ready(Ok(ChunkedState::Extension)),
@@ -449,7 +462,7 @@ mod tests {
449462

450463
#[tokio::test]
451464
async fn test_read_chunk_size() {
452-
use std::io::ErrorKind::{InvalidInput, UnexpectedEof};
465+
use std::io::ErrorKind::{InvalidData, InvalidInput, UnexpectedEof};
453466

454467
async fn read(s: &str) -> u64 {
455468
let mut state = ChunkedState::Size;
@@ -524,6 +537,8 @@ mod tests {
524537
read_err("1 invalid extension\r\n", InvalidInput).await;
525538
read_err("1 A\r\n", InvalidInput).await;
526539
read_err("1;no CRLF", UnexpectedEof).await;
540+
// Overflow
541+
read_err("f0000000000000003\r\n", InvalidData).await;
527542
}
528543

529544
#[tokio::test]

‎tests/server.rs

+25
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,31 @@ fn post_with_chunked_body() {
431431
assert_eq!(server.body(), b"qwert");
432432
}
433433

434+
#[test]
435+
fn post_with_chunked_overflow() {
436+
let server = serve();
437+
let mut req = connect(server.addr());
438+
req.write_all(
439+
b"\
440+
POST / HTTP/1.1\r\n\
441+
Host: example.domain\r\n\
442+
Transfer-Encoding: chunked\r\n\
443+
\r\n\
444+
f0000000000000003\r\n\
445+
abc\r\n\
446+
0\r\n\
447+
\r\n\
448+
GET /sneaky HTTP/1.1\r\n\
449+
\r\n\
450+
",
451+
)
452+
.unwrap();
453+
req.read(&mut [0; 256]).unwrap();
454+
455+
let err = server.body_err().to_string();
456+
assert!(err.contains("overflow"), "error should be overflow: {:?}", err);
457+
}
458+
434459
#[test]
435460
fn post_with_incomplete_body() {
436461
let _ = pretty_env_logger::try_init();

0 commit comments

Comments
 (0)
Please sign in to comment.