{"record":{"id":"67520ddf4f8b226c","repo":"grpc/grpc-go","slug":"cannot-slice-freed-buffer","errorCode":null,"errorMessage":"Cannot slice freed buffer","messagePattern":"Cannot slice freed buffer","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"mem/buffers.go","lineNumber":189,"sourceCode":"\t\t}\n\t\tb.origData = nil\n\t} else {\n\t\t// This buffer doesn't own the data slice, decrement a ref on the root\n\t\t// buffer.\n\t\tb.rootBuf.Free()\n\t}\n\n\tb.rootBuf = nil\n\tbufferObjectPool.Put(b)\n}\n\nfunc (b *buffer) Len() int {\n\treturn len(b.ReadOnlyData())\n}\n\nfunc (b *buffer) Slice(start, end int) Buffer {\n\tif b.rootBuf == nil {\n\t\tpanic(\"Cannot slice freed buffer\")\n\t}\n\n\tdata := b.data[start:end] // access the data to check slice bounds\n\n\tif len(data) == 0 {\n\t\treturn emptyBuffer{}\n\t}\n\tif len(data) == len(b.data) {\n\t\tb.Ref()\n\t\treturn b\n\t}\n\t// We are creating a new reference (view) to a portion of the root buffer's\n\t// data. Therefore, we must increment the reference count of the root buffer\n\t// to ensure the underlying data is not freed while this view is still in\n\t// use.\n\tb.rootBuf.Ref()\n\ts := newBuffer()\n\ts.data = data","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/mem/buffers.go#L171-L207","documentation":"buffer.Slice(start, end) (mem/buffers.go:187) returns a new view into the buffer's data and panics at line 188-190 if b.rootBuf == nil (buffer already freed). Slicing freed memory would point at pooled/recycled bytes, so it is rejected. A successful Slice also takes a Ref() on the root (line 205) so the view keeps the data alive.","triggerScenarios":"Calling buf.Slice(a, b) after buf.Free(); slicing a buffer whose data was freed by another goroutine; slicing within a CodecV2 after the input BufferSlice was freed.","commonSituations":"Custom CodecV2/interceptor that slices input buffers after they were freed by the framework; concurrent stream processing where one path freed the buffer; reusing a buffer variable across iterations after freeing it.","solutions":["Slice the buffer only while it is alive (before Free, or on a Ref()'d copy).","If you need the sliced view beyond the current scope, take a Ref() first and Free() when done.","Treat the input BufferSlice in CodecV2 as freed once Unmarshal returns; copy or Ref() anything you retain."],"exampleFix":"// before\nb.Free()\nsub := b.Slice(0, 4) // panic: freed\n\n// after\nsub := b.Slice(0, 4) // slice while alive (Ref taken on root)\nb.Free()             // free original ref; sub keeps data alive\n// later: sub.Free()","handlingStrategy":"validation","validationCode":"// Slice only while alive; take a Ref first if lifetime is uncertain.\nfunc safeSlice(b mem.Buffer, start, end int) mem.Buffer {\n    if b.Len() == 0 || start < 0 || end > b.Len() || start > end {\n        return nil\n    }\n    return b.Slice(start, end)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Slice before Free, or on a Ref()'d copy.","Remember Slice takes its own Ref on the root; free the view when done.","Treat input buffers in CodecV2 as freed once Unmarshal returns."],"tags":["mem","buffer","use-after-free","panic","go"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}