{"id":"c07baf441e9fc7fd","repo":"hyperium/hyper","slug":"chunk-extensions-over-limit","errorCode":null,"errorMessage":"chunk extensions over limit","messagePattern":"chunk extensions over limit","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src/proto/h1/decode.rs","lineNumber":444,"sourceCode":"        extensions_cnt: &mut u64,\n    ) -> Poll<Result<ChunkedState, io::Error>> {\n        trace!(\"read_extension\");\n        // We don't care about extensions really at all. Just ignore them.\n        // They \"end\" at the next CRLF.\n        //\n        // However, some implementations may not check for the CR, so to save\n        // them from themselves, we reject extensions containing plain LF as\n        // well.\n        match byte!(rdr, cx) {\n            b'\\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),\n            b'\\n' => Poll::Ready(Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                \"invalid chunk extension contains newline\",\n            ))),\n            _ => {\n                *extensions_cnt += 1;\n                if *extensions_cnt >= CHUNKED_EXTENSIONS_LIMIT {\n                    Poll::Ready(Err(io::Error::new(\n                        io::ErrorKind::InvalidData,\n                        \"chunk extensions over limit\",\n                    )))\n                } else {\n                    Poll::Ready(Ok(ChunkedState::Extension))\n                }\n            } // no supported extensions\n        }\n    }\n    fn read_size_lf<R: MemRead>(\n        cx: &mut Context<'_>,\n        rdr: &mut R,\n        size: u64,\n    ) -> Poll<Result<ChunkedState, io::Error>> {\n        trace!(\"Chunk size is {:?}\", size);\n        match byte!(rdr, cx) {\n            b'\\n' => {\n                if size == 0 {","sourceCodeStart":426,"sourceCodeEnd":462,"githubUrl":"https://github.com/hyperium/hyper/blob/084473f728f9d07b3be5845475aa2f62ed9ff579/src/proto/h1/decode.rs#L426-L462","documentation":"Thrown in read_extension (src/proto/h1/decode.rs:444) when the running total of chunk-extension bytes (extensions_cnt) reaches CHUNKED_EXTENSIONS_LIMIT, a hard-coded 16 KiB cap applied across the whole body (see decode.rs:20). hyper does not parse extensions but bounds them to limit memory/wire abuse. Reported as io::ErrorKind::InvalidData.","triggerScenarios":"A chunked body whose combined extension text (everything after ';' on every chunk-size line, summed over all chunks) reaches 16384 bytes — e.g. many small chunks each carrying a large correlation-id extension, or one chunk with a >16KB extension blob.","commonSituations":"Tracing/observability proxies that attach large trace-state or baggage to every chunk; a sender that embeds base64 payloads in extensions; abuse/DoS where an attacker stuffs extensions to exhaust memory.","solutions":["Move large per-chunk metadata out of chunk extensions and into trailers or the body itself.","Reduce the number of chunks (batch data) so extensions aren't repeated hundreds of times.","If you are on the receiving side and the limit is a defense, leave it; if you genuinely need more, the limit is a const (CHUNKED_EXTENSIONS_LIMIT) and requires a hyper patch/rebuild.","Front the service with a proxy that strips oversized extensions before they reach hyper."],"exampleFix":"// before: stuffing trace context into every chunk extension\nfor part in parts {\n    write!(w, \"{:x};trace={}\\r\\n\", part.len(), huge_trace).await?;\n    w.write_all(&part).await?;\n    write!(w, \"\\r\\n\").await?;\n}\n\n// after: send trace once as a trailer\nwrite!(w, \"0\\r\\ntrace: {}\\r\\n\\r\\n\", huge_trace).await?;","handlingStrategy":"try-catch","validationCode":"// When emitting many chunks, keep total extension bytes under 16 KiB.\nconst EXT_BUDGET: u64 = 16 * 1024;\nlet mut spent: u64 = 0;\nfor part in parts {\n    let ext_len = trace_str.len() as u64;\n    if spent + ext_len >= EXT_BUDGET {\n        // stop emitting extensions; rely on a trailer instead\n        write!(w, \"{:x}\\r\\n\", part.len()).await?;\n    } else {\n        write!(w, \"{:x};t={}\\r\\n\", part.len(), trace_str).await?;\n        spent += ext_len;\n    }\n}","typeGuard":null,"tryCatchPattern":"Some(Err(e)) => {\n    if e.to_string().contains(\"chunk extensions over limit\") {\n        metrics::increment!(\"hyper.chunked.ext_over_limit\");\n        tracing::warn!(error=%e, \"peer exceeded 16KB chunk-extension budget\");\n        break;\n    }\n    return Err(e.into());\n}","preventionTips":["Keep per-chunk extensions tiny or omit them; put metadata in a single trailer.","Batch data into fewer chunks to avoid repeating extensions hundreds of times.","Monitor 'chunk extensions over limit' as a DoS signal on public endpoints."],"tags":["http","http1","chunked","extension","limits","hyper","rust"],"analyzedSha":"084473f728f9d07b3be5845475aa2f62ed9ff579","analyzedAt":"2026-08-06T01:20:18.522Z","schemaVersion":2}