{"record":{"id":"476dee24b4b01e81","repo":"grpc/grpc-go","slug":"cannot-split-freed-buffer","errorCode":null,"errorMessage":"Cannot split freed buffer","messagePattern":"Cannot split freed buffer","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"mem/buffers.go","lineNumber":215,"sourceCode":"\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\n\ts.rootBuf = b.rootBuf\n\ts.refs.Store(1)\n\treturn s\n}\n\nfunc (b *buffer) split(n int) (Buffer, Buffer) {\n\tif b.rootBuf == nil || b.rootBuf.refs.Add(1) <= 1 {\n\t\tpanic(\"Cannot split freed buffer\")\n\t}\n\n\tsplit := newBuffer()\n\tsplit.data = b.data[n:]\n\tsplit.rootBuf = b.rootBuf\n\tsplit.refs.Store(1)\n\n\tb.data = b.data[:n]\n\n\treturn b, split\n}\n\nfunc (b *buffer) read(buf []byte) (int, Buffer) {\n\tif b.rootBuf == nil {\n\t\tpanic(\"Cannot read freed buffer\")\n\t}\n\n\tn := copy(buf, b.data)","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/mem/buffers.go#L197-L233","documentation":"buffer.split(n) (mem/buffers.go:213) splits the buffer at offset n into a left/right view and panics at line 214-216 if b.rootBuf == nil or if incrementing the root's refcount yields <= 1 (root already freed). Exposed via mem.SplitUnsafe. Splitting freed memory would alias pooled bytes, so it is rejected.","triggerScenarios":"Calling mem.SplitUnsafe(buf, n) after buf.Free(); splitting a buffer in a codec after the framework freed the input; splitting a buffer that was already freed by a concurrent goroutine.","commonSituations":"Custom streaming codecs/interceptors that call SplitUnsafe on input buffers past their lifetime; reusing a freed buffer variable; refcount mis-management across goroutines.","solutions":["Call SplitUnsafe only on buffers known to be alive; Ref() first if lifetime is uncertain.","Free each of the two returned references exactly once when done.","Avoid splitting input buffers after the API that owns them has returned."],"exampleFix":"// before\nb.Free()\nleft, right := mem.SplitUnsafe(b, 4) // panic: freed\n\n// after\nleft, right := mem.SplitUnsafe(b, 4) // split while alive\nb.Free() // b's ref consumed; left/right hold their own refs\n// later: left.Free(); right.Free()","handlingStrategy":"validation","validationCode":"// SplitUnsafe only on live buffers.\nfunc safeSplit(b mem.Buffer, n int) (mem.Buffer, mem.Buffer) {\n    if b.Len() == 0 || n < 0 || n > b.Len() {\n        return nil, nil\n    }\n    return mem.SplitUnsafe(b, n)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ref() before splitting if lifetime is uncertain.","Free each returned reference exactly once.","Never split buffers after the owning API has returned."],"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-14T05:17:10.506Z"}