hyperium/hyper · error · std::io::Error
invalid chunk extension contains newline
Error message
invalid chunk extension contains newline
What it means
Thrown in the Extension state (src/proto/h1/decode.rs:437) when a chunk extension contains a bare line-feed ('\n') not preceded by '\r'. hyper ignores chunk extension contents but defensively rejects a lone LF (which some broken senders use instead of CRLF) to avoid smuggling ambiguity. Reported as io::ErrorKind::InvalidData.
Source
Thrown at src/proto/h1/decode.rs:437
"Invalid chunk size linear white space",
))),
}
}
fn read_extension<R: MemRead>(
cx: &mut Context<'_>,
rdr: &mut R,
extensions_cnt: &mut u64,
) -> Poll<Result<ChunkedState, io::Error>> {
trace!("read_extension");
// We don't care about extensions really at all. Just ignore them.
// They "end" at the next CRLF.
//
// However, some implementations may not check for the CR, so to save
// them from themselves, we reject extensions containing plain LF as
// well.
match byte!(rdr, cx) {
b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
b'\n' => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid chunk extension contains newline",
))),
_ => {
*extensions_cnt += 1;
if *extensions_cnt >= CHUNKED_EXTENSIONS_LIMIT {
Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidData,
"chunk extensions over limit",
)))
} else {
Poll::Ready(Ok(ChunkedState::Extension))
}
} // no supported extensions
}
}
fn read_size_lf<R: MemRead>(
cx: &mut Context<'_>,View on GitHub (pinned to 084473f728)
Solutions
- Ensure chunk extensions are terminated by CRLF (\r\n), never a lone LF.
- Strip CR/LF from any dynamic value placed into a chunk extension before writing it.
- If you do not need chunk extensions, omit the ';' entirely.
Example fix
// before: extension value contains a newline
let trace = "a\nb";
write!(w, "1;t={}\r\nX\r\n", trace).await?; // -> error 23
// after: sanitize extension bytes
let trace = trace.replace(['\r', '\n'], "_");
write!(w, "1;t={}\r\nX\r\n", trace).await?; Defensive patterns
Strategy: validation
Validate before calling
// Before writing a chunk extension, strip CR/LF so no bare LF can reach the wire.
fn safe_ext(value: &str) -> String {
value.chars().map(|c| match c {
'\r' | '\n' => '_',
c => c,
}).collect()
}
write!(w, "{:x};t={}\r\n", len, safe_ext(&trace_id)).await?; Type guard
fn has_no_newline(s: &str) -> bool { !s.as_bytes().iter().any(|b| matches!(b, b'\r' | b'\n')) } Try / catch
Some(Err(e)) => {
let kind = e.source()
.and_then(|s| s.downcast_ref::<std::io::Error>())
.map(|io| io.kind());
if matches!(kind, Some(std::io::ErrorKind::InvalidData))
&& e.to_string().contains("newline") {
tracing::warn!(error=%e, "peer sent LF inside chunk extension; possible smuggling");
break;
}
return Err(e.into());
} Prevention
- Sanitize any dynamic value placed in a chunk extension (strip CR/LF/NUL).
- Treat a bare-LF-in-extension as a security signal: log and rate-limit the source.
- Prefer trailers over chunk extensions for structured metadata.
When it happens
Trigger: A chunk extension that uses bare LF as a separator, e.g. a chunk line "1;reject\nnewlines\r\n" where the extension text contains '\n' before the terminating CRLF.
Common situations: A peer that normalizes CRLF to LF in the body; a logging/trace value embedded in a chunk extension that contains newlines; request smuggling attempts that exploit LF handling.
Related errors
- chunk extensions over limit
- Invalid chunk size line: missing size digit
- Invalid chunk size line: Invalid Size
- Invalid chunk size linear white space
- Invalid chunk size LF
AI-assisted analysis of hyperium/hyper@084473f728 (2026-08-06).
Data as JSON: /data/errors/24c6f61f75b6cc60.json.
Report an issue: GitHub.