Skip to content

Commit 655d7b4

Browse files
committed
sec fixes
1 parent 24d525d commit 655d7b4

File tree

2 files changed

+59
-26
lines changed

2 files changed

+59
-26
lines changed

actix-http/src/h1/decoder.rs

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ pub(crate) trait MessageType: Sized {
6767
let mut has_upgrade_websocket = false;
6868
let mut expect = false;
6969
let mut chunked = false;
70+
let mut seen_te = false;
7071
let mut content_length = None;
7172

7273
{
@@ -85,8 +86,17 @@ pub(crate) trait MessageType: Sized {
8586
};
8687

8788
match name {
88-
header::CONTENT_LENGTH => {
89-
if let Ok(s) = value.to_str() {
89+
header::CONTENT_LENGTH if content_length.is_some() => {
90+
debug!("multiple Content-Length");
91+
return Err(ParseError::Header);
92+
}
93+
94+
header::CONTENT_LENGTH => match value.to_str() {
95+
Ok(s) if s.trim().starts_with("+") => {
96+
debug!("illegal Content-Length: {:?}", s);
97+
return Err(ParseError::Header);
98+
}
99+
Ok(s) => {
90100
if let Ok(len) = s.parse::<u64>() {
91101
if len != 0 {
92102
content_length = Some(len);
@@ -95,15 +105,31 @@ pub(crate) trait MessageType: Sized {
95105
debug!("illegal Content-Length: {:?}", s);
96106
return Err(ParseError::Header);
97107
}
98-
} else {
108+
}
109+
Err(_) => {
99110
debug!("illegal Content-Length: {:?}", value);
100111
return Err(ParseError::Header);
101112
}
102-
}
113+
},
114+
103115
// transfer-encoding
116+
header::TRANSFER_ENCODING if seen_te => {
117+
debug!("multiple Transfer-Encoding not allowed");
118+
return Err(ParseError::Header);
119+
}
120+
104121
header::TRANSFER_ENCODING => {
122+
seen_te = true;
123+
105124
if let Ok(s) = value.to_str().map(|s| s.trim()) {
106-
chunked = s.eq_ignore_ascii_case("chunked");
125+
if s.eq_ignore_ascii_case("chunked") {
126+
chunked = true;
127+
} else if s.eq_ignore_ascii_case("identity") {
128+
// allow silently since multiple TE headers are already checked
129+
} else {
130+
debug!("illegal Transfer-Encoding: {:?}", s);
131+
return Err(ParseError::Header);
132+
}
107133
} else {
108134
return Err(ParseError::Header);
109135
}
@@ -510,19 +536,11 @@ impl ChunkedState {
510536
size: &mut u64,
511537
) -> Poll<Result<ChunkedState, io::Error>> {
512538
let radix = 16;
513-
match byte!(rdr) {
514-
b @ b'0'..=b'9' => {
515-
*size *= radix;
516-
*size += u64::from(b - b'0');
517-
}
518-
b @ b'a'..=b'f' => {
519-
*size *= radix;
520-
*size += u64::from(b + 10 - b'a');
521-
}
522-
b @ b'A'..=b'F' => {
523-
*size *= radix;
524-
*size += u64::from(b + 10 - b'A');
525-
}
539+
540+
let rem = match byte!(rdr) {
541+
b @ b'0'..=b'9' => b - b'0',
542+
b @ b'a'..=b'f' => b + 10 - b'a',
543+
b @ b'A'..=b'F' => b + 10 - b'A',
526544
b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)),
527545
b';' => return Poll::Ready(Ok(ChunkedState::Extension)),
528546
b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)),
@@ -532,8 +550,23 @@ impl ChunkedState {
532550
"Invalid chunk size line: Invalid Size",
533551
)));
534552
}
553+
};
554+
555+
match size.checked_mul(radix) {
556+
Some(n) => {
557+
*size = n as u64;
558+
*size += rem as u64;
559+
560+
Poll::Ready(Ok(ChunkedState::Size))
561+
}
562+
None => {
563+
debug!("chunk size would overflow");
564+
Poll::Ready(Err(io::Error::new(
565+
io::ErrorKind::InvalidInput,
566+
"Invalid chunk size line: Invalid Size",
567+
)))
568+
}
535569
}
536-
Poll::Ready(Ok(ChunkedState::Size))
537570
}
538571

539572
fn read_size_lws(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
@@ -552,6 +585,11 @@ impl ChunkedState {
552585
fn read_extension(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
553586
match byte!(rdr) {
554587
b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
588+
// strictly 0x20 (space) should be disallowed but we don't parse quoted strings here
589+
0x00..=0x08 | 0x0a..=0x1f | 0x7f => Poll::Ready(Err(io::Error::new(
590+
io::ErrorKind::InvalidInput,
591+
"Invalid character in chunk extension",
592+
))),
555593
_ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
556594
}
557595
}
@@ -977,13 +1015,7 @@ mod tests {
9771015
"GET /test HTTP/1.1\r\n\
9781016
transfer-encoding: chnked\r\n\r\n",
9791017
);
980-
let req = parse_ready!(&mut buf);
981-
982-
if let Ok(val) = req.chunked() {
983-
assert!(!val);
984-
} else {
985-
unreachable!("Error");
986-
}
1018+
expect_parse_err!(&mut buf);
9871019
}
9881020

9891021
#[test]

actix-http/src/h1/encoder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub(crate) trait MessageType: Sized {
8080
match length {
8181
BodySize::Stream => {
8282
if chunked {
83+
skip_len = true;
8384
if camel_case {
8485
dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n")
8586
} else {

0 commit comments

Comments
 (0)