Skip to content

Commit 5eca028

Browse files
committed
fix(http1): reject chunked headers missing a digit (#3494)
Previously, hyper would decode `\r\n\r\n` as `0\r\n\r\n`. This fixes hyper to require a digit to be present before starting at 0. Reported-by: Ben Kallus <benjamin.p.kallus.gr@dartmouth.edu>
1 parent 7f382ad commit 5eca028

File tree

1 file changed

+56
-15
lines changed

1 file changed

+56
-15
lines changed

src/proto/h1/decode.rs

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ enum Kind {
4848

4949
#[derive(Debug, PartialEq, Clone, Copy)]
5050
enum ChunkedState {
51+
Start,
5152
Size,
5253
SizeLws,
5354
Extension,
@@ -73,7 +74,7 @@ impl Decoder {
7374

7475
pub(crate) fn chunked() -> Decoder {
7576
Decoder {
76-
kind: Kind::Chunked(ChunkedState::Size, 0),
77+
kind: Kind::Chunked(ChunkedState::new(), 0),
7778
}
7879
}
7980

@@ -181,7 +182,22 @@ macro_rules! byte (
181182
})
182183
);
183184

185+
macro_rules! or_overflow {
186+
($e:expr) => (
187+
match $e {
188+
Some(val) => val,
189+
None => return Poll::Ready(Err(io::Error::new(
190+
io::ErrorKind::InvalidData,
191+
"invalid chunk size: overflow",
192+
))),
193+
}
194+
)
195+
}
196+
184197
impl ChunkedState {
198+
fn new() -> ChunkedState {
199+
ChunkedState::Start
200+
}
185201
fn step<R: MemRead>(
186202
&self,
187203
cx: &mut Context<'_>,
@@ -191,6 +207,7 @@ impl ChunkedState {
191207
) -> Poll<Result<ChunkedState, io::Error>> {
192208
use self::ChunkedState::*;
193209
match *self {
210+
Start => ChunkedState::read_start(cx, body, size),
194211
Size => ChunkedState::read_size(cx, body, size),
195212
SizeLws => ChunkedState::read_size_lws(cx, body),
196213
Extension => ChunkedState::read_extension(cx, body),
@@ -205,25 +222,46 @@ impl ChunkedState {
205222
End => Poll::Ready(Ok(ChunkedState::End)),
206223
}
207224
}
208-
fn read_size<R: MemRead>(
225+
226+
fn read_start<R: MemRead>(
209227
cx: &mut Context<'_>,
210228
rdr: &mut R,
211229
size: &mut u64,
212230
) -> Poll<Result<ChunkedState, io::Error>> {
213-
trace!("Read chunk hex size");
231+
trace!("Read chunk start");
214232

215-
macro_rules! or_overflow {
216-
($e:expr) => (
217-
match $e {
218-
Some(val) => val,
219-
None => return Poll::Ready(Err(io::Error::new(
220-
io::ErrorKind::InvalidData,
221-
"invalid chunk size: overflow",
222-
))),
223-
}
224-
)
233+
let radix = 16;
234+
match byte!(rdr, cx) {
235+
b @ b'0'..=b'9' => {
236+
*size = or_overflow!(size.checked_mul(radix));
237+
*size = or_overflow!(size.checked_add((b - b'0') as u64));
238+
}
239+
b @ b'a'..=b'f' => {
240+
*size = or_overflow!(size.checked_mul(radix));
241+
*size = or_overflow!(size.checked_add((b + 10 - b'a') as u64));
242+
}
243+
b @ b'A'..=b'F' => {
244+
*size = or_overflow!(size.checked_mul(radix));
245+
*size = or_overflow!(size.checked_add((b + 10 - b'A') as u64));
246+
}
247+
_ => {
248+
return Poll::Ready(Err(io::Error::new(
249+
io::ErrorKind::InvalidInput,
250+
"Invalid chunk size line: missing size digit",
251+
)));
252+
}
225253
}
226254

255+
Poll::Ready(Ok(ChunkedState::Size))
256+
}
257+
258+
fn read_size<R: MemRead>(
259+
cx: &mut Context<'_>,
260+
rdr: &mut R,
261+
size: &mut u64,
262+
) -> Poll<Result<ChunkedState, io::Error>> {
263+
trace!("Read chunk hex size");
264+
227265
let radix = 16;
228266
match byte!(rdr, cx) {
229267
b @ b'0'..=b'9' => {
@@ -478,7 +516,7 @@ mod tests {
478516
use std::io::ErrorKind::{InvalidData, InvalidInput, UnexpectedEof};
479517

480518
async fn read(s: &str) -> u64 {
481-
let mut state = ChunkedState::Size;
519+
let mut state = ChunkedState::new();
482520
let rdr = &mut s.as_bytes();
483521
let mut size = 0;
484522
loop {
@@ -495,7 +533,7 @@ mod tests {
495533
}
496534

497535
async fn read_err(s: &str, expected_err: io::ErrorKind) {
498-
let mut state = ChunkedState::Size;
536+
let mut state = ChunkedState::new();
499537
let rdr = &mut s.as_bytes();
500538
let mut size = 0;
501539
loop {
@@ -532,6 +570,9 @@ mod tests {
532570
// Missing LF or CRLF
533571
read_err("F\rF", InvalidInput).await;
534572
read_err("F", UnexpectedEof).await;
573+
// Missing digit
574+
read_err("\r\n\r\n", InvalidInput).await;
575+
read_err("\r\n", InvalidInput).await;
535576
// Invalid hex digit
536577
read_err("X\r\n", InvalidInput).await;
537578
read_err("1X\r\n", InvalidInput).await;

0 commit comments

Comments
 (0)