{"record":{"id":"fe66e8b79100ae58","repo":"cloudflare/pingora","slug":"body-buf-is-initialized-before-reading-trailers","errorCode":null,"errorMessage":"body buf is initialized before reading trailers","messagePattern":"body buf is initialized before reading trailers","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pingora-core/src/protocols/http/v1/body.rs","lineNumber":849,"sourceCode":"                        .await\n                        .or_err(ReadError, \"when reading trailers end\")?\n                };\n                if n == 0 {\n                    self.body_state = PS::Done(read);\n                    return Error::e_explain(\n                        ConnectionClosed,\n                        format!(\n                            \"Connection prematurely closed without the termination chunk, \\\n                            read {read} bytes, {trailers_read} trailer bytes\"\n                        ),\n                    );\n                }\n\n                // Re-borrow just the buffer field so `self.body_state` stays assignable below.\n                let buf = &self\n                    .body_buf\n                    .as_deref()\n                    .expect(\"body buf is initialized before reading trailers\")[..n];\n\n                let mut start = 0;\n                // try to find end within the current IO buffer\n                while start < n {\n                    // Adjusts body state through each iteration to add trailers read\n                    // Each iteration finds the next CR or LF to advance the buf\n                    let (trailers_read, end_read) = match self.body_state {\n                        PS::ChunkedFinal(_, new_trailers_read, _, new_end_read) => {\n                            (new_trailers_read, new_end_read)\n                        }\n                        _ => unreachable!(),\n                    };\n\n                    let mut buf = &buf[start..n];\n                    trace!(\n                        \"Parsing chunk end for buf {:?}\",\n                        String::from_utf8_lossy(buf).escape_default(),\n                    );","sourceCodeStart":831,"sourceCodeEnd":867,"githubUrl":"https://github.com/cloudflare/pingora/blob/0046038bd402bc82912da862dadf9a479f31e9f1/pingora-core/src/protocols/http/v1/body.rs#L831-L867","documentation":"Internal invariant panic in pingora's HTTP/1.1 body reader. After entering the ChunkedFinal state to parse the trailer section of a chunked message, the code re-borrows self.body_buf with as_deref().expect(): the reader's state machine guarantees the buffer exists whenever the trailer loop runs. Hitting this panic means pingora's internal state tracking became inconsistent, not that your application logic passed bad input.","triggerScenarios":"Reading trailers of a chunked HTTP/1.1 request/response (state ChunkedFinal) when body_buf was left None/empty before the loop re-borrows it. Typically surfaced by fuzzers feeding mutated chunked bodies with trailer sections split across reads, or by a pingora version with a regression in the BodyReader state machine (e.g. buffer taken/consumed on an error path before trailer parsing re-runs).","commonSituations":"Fuzzing pingora with trailer-bearing chunked encodings; upgrading between pingora versions that changed body.rs state handling; custom ProxyHttp/HTTP filters that call body-reading APIs in unusual orders or recover from partial body reads and resume reading trailers.","solutions":["Pin/upgrade to a pingora release where the trailer state machine is known-good and check the changelog for fixes touching pingora-core/src/protocols/http/v1/body.rs","Capture the failing stream (tcpdump) to get the exact chunked+trailer byte sequence for a minimal reproduction","Report the reproduction to the pingora issue tracker — this expect is an internal invariant, not input validation","If it fires under your own filter code, audit anything that touches the session body reader or swallows body-read errors before trailers are parsed"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Panics in per-connection tasks are contained by tokio: observe them via the task handle\nlet handle = tokio::spawn(async move { /* connection / test driving pingora */ });\nif let Err(join_err) = handle.await {\n    if join_err.is_panic() {\n        // log with RUST_BACKTRACE=1 context; the connection died, the service lives on\n        tracing::error!(\"connection task panicked: {join_err}\");\n    }\n}","preventionTips":["Run regression tests with trailer-bearing chunked responses when upgrading pingora","Fuzz deployments with mutated chunked+trailer bodies before shipping version bumps","Install a panic hook that logs peer and stream context so this invariant panic is diagnosable","Watch per-connection task panics via JoinError instead of letting them vanish silently"],"tags":["rust","pingora","http1","chunked-encoding","trailers","panic","internal-invariant"],"backgroundTag":"http-chunked-trailer-parsing","analyzedSha":"0046038bd402bc82912da862dadf9a479f31e9f1","analyzedAt":"2026-08-16T21:33:22.341Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}