{"record":{"id":"a8720bace71a4a1d","repo":"cloudflare/pingora","slug":"must-have-read-body-buf","errorCode":null,"errorMessage":"must have read body buf","messagePattern":"must have read body buf","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pingora-core/src/protocols/http/v1/body.rs","lineNumber":369,"sourceCode":"            Some(buf) => buf.extend_from_slice(bytes),\n            None => {\n                let mut buf = BytesMut::with_capacity(bytes.len());\n                buf.extend_from_slice(bytes);\n                self.body_buf_overread = Some(buf);\n            }\n        }\n    }\n\n    pub fn body_done(&self) -> bool {\n        matches!(self.body_state, PS::Complete(_) | PS::Done(_))\n    }\n\n    pub fn body_empty(&self) -> bool {\n        self.body_state == PS::Complete(0)\n    }\n\n    fn finish_body_buf(&mut self, end_of_body: usize, total_read: usize) {\n        let body_buf_mut = self.body_buf.as_mut().expect(\"must have read body buf\");\n        // remove unused buffer\n        body_buf_mut.truncate(total_read);\n        let overread_bytes = body_buf_mut.split_off(end_of_body);\n        self.body_buf_overread = (!overread_bytes.is_empty()).then_some(overread_bytes);\n    }\n\n    pub async fn read_body<S>(&mut self, stream: &mut S) -> Result<Option<BufRef>>\n    where\n        S: AsyncRead + Unpin + Send,\n    {\n        match self.body_state {\n            PS::Complete(_) => Ok(None),\n            PS::Done(_) => Ok(None),\n            PS::Partial(_, _) => self.do_read_body(stream).await,\n            PS::Chunked(..) => self.do_read_chunked_body(stream).await,\n            PS::ChunkedFinal(..) => self.do_read_chunked_body_final(stream).await,\n            PS::UntilClose(_) => self.do_read_body_until_closed(stream).await,\n            PS::ToStart => panic!(\"need to init BodyReader first\"),","sourceCodeStart":351,"sourceCodeEnd":387,"githubUrl":"https://github.com/cloudflare/pingora/blob/0046038bd402bc82912da862dadf9a479f31e9f1/pingora-core/src/protocols/http/v1/body.rs#L351-L387","documentation":"finish_body_buf() is an internal helper of pingora's HTTP/1.1 body reader (protocols/http/v1/body.rs:369): when a body read completes it truncates the shared read buffer and splits off overread bytes, assuming self.body_buf was allocated during reading. The .expect()('must have read body buf') fires when the reader's state machine reaches the finish step with no buffer allocated — an internal invariant violation, not a documented user error.","triggerScenarios":"Any code path that drives Http1Body to body completion without a prior buffered read allocating body_buf — in practice triggered by a pingora bug or by custom filters that reenter/manipulate the downstream or upstream session body state unexpectedly. Surfaces as a panic (502/reset) while proxying an HTTP/1.1 response.","commonSituations":"Upgrading between pingora versions with parser refactors; unusual HTTP/1.1 traffic (zero-length bodies, early end-of-stream combinations) hitting an untested state; downstream code misusing session body readers concurrently.","solutions":["Upgrade pingora-core to the latest patch release; invariant panics in the H1 body reader are bug candidates that get fixed","If it reproduces, capture the exact request/response bytes (pcap or a logging proxy) and open an issue with pingora including the trace","Audit any custom request/response body filters for reentrant or out-of-order reads of the session body"],"exampleFix":"# before: pinned to an older pingora-core with the parser defect\npingora-core = \"=0.7.0\"\n\n# after: pick up the fix\ncargo update -p pingora-core","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Contain H1 body parser panics to a single connection task\nlet resp = tokio::spawn(async move {\n    std::panic::catch_unwind(std::panic::AssertUnwindSafe(async {\n        session.read_body().await // any H1 body read path\n    }))\n    .await\n    .unwrap_or_else(|panic| {\n        log::error!(\"H1 body parser panicked: {panic:?}\");\n        Err(internal_error_502())\n    })\n});","preventionTips":["Run connection tasks in spawned tasks so a parser panic resets one connection, not the worker","Fuzz HTTP/1.1 traffic (malformed bodies, zero-length, early close) in CI against your build","Pin tested pingora-core versions and upgrade promptly when H1 parser fixes ship","Keep pcaps/request logs on failures so internal panics can be reported with a reproducer"],"tags":["rust","http1","body-parser","chunked","pingora","invariant","panic"],"backgroundTag":"http-body-parser-panic","analyzedSha":"0046038bd402bc82912da862dadf9a479f31e9f1","analyzedAt":"2026-08-16T21:33:22.341Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}