{"record":{"id":"d51935db2d3f7287","repo":"grpc/grpc-go","slug":"cannot-read-freed-buffer","errorCode":null,"errorMessage":"Cannot read freed buffer","messagePattern":"Cannot read freed buffer","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"mem/buffers.go","lineNumber":144,"sourceCode":"//\n// It acquires a []byte from the given pool and copies over the backing array\n// of the given data. The []byte acquired from the pool is returned to the\n// pool when all references to the returned Buffer are released.\nfunc Copy(data []byte, pool BufferPool) Buffer {\n\tif IsBelowBufferPoolingThreshold(len(data)) {\n\t\tbuf := make(SliceBuffer, len(data))\n\t\tcopy(buf, data)\n\t\treturn buf\n\t}\n\n\tbuf := pool.Get(len(data))\n\tcopy(*buf, data)\n\treturn NewBuffer(buf, pool)\n}\n\nfunc (b *buffer) ReadOnlyData() []byte {\n\tif b.rootBuf == nil {\n\t\tpanic(\"Cannot read freed buffer\")\n\t}\n\treturn b.data\n}\n\nfunc (b *buffer) Ref() {\n\tif b.refs.Add(1) <= 1 {\n\t\tpanic(\"Cannot ref freed buffer\")\n\t}\n}\n\nfunc (b *buffer) Free() {\n\trefs := b.refs.Add(-1)\n\tif refs < 0 {\n\t\tpanic(\"Cannot free freed buffer\")\n\t}\n\tif refs > 0 {\n\t\treturn\n\t}","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/mem/buffers.go#L126-L162","documentation":"mem.Buffer is reference-counted. ReadOnlyData() (mem/buffers.go:142) panics at line 143-145 when b.rootBuf == nil, which Free() clears (line 179) once the refcount hits zero and the backing slice is returned to the BufferPool. This is a use-after-free guard: reading data after Free() would observe pooled/recycled memory, causing corruption and data races. The Buffer doc (line 39-44) states each goroutine must take its own reference via Ref() and that access after release panics.","triggerScenarios":"Calling buf.Free() then later buf.ReadOnlyData(); a custom CodecV2.Unmarshal that keeps a reference to the data slice past the function return (the package frees data as soon as Unmarshal returns, per encoding_v2.go:35-38); sharing one Buffer across goroutines where one frees it while another reads.","commonSituations":"Implementing a CodecV2 that stashes ReadOnlyData() for async use; keeping a returned []byte from ReadOnlyData() around after the buffer was freed; double-Free across goroutines; converting a Codec to CodecV2 without copying retained data.","solutions":["Call Ref() to take your own reference before sharing or retaining a buffer; call Free() on your reference when done.","Inside CodecV2.Unmarshal, copy any bytes you must keep past the return (the docs explicitly require this).","Never access a buffer after calling Free() on it; treat Free() as the end of its lifetime."],"exampleFix":"// before (custom CodecV2.Unmarshal keeps a slice past return -> freed)\nvar stash []byte\nfunc (c myCodec) Unmarshal(data mem.BufferSlice, v any) error {\n    stash = data[0].ReadOnlyData() // freed after return -> later panic\n    return nil\n}\n\n// after\nfunc (c myCodec) Unmarshal(data mem.BufferSlice, v any) error {\n    src := data[0].ReadOnlyData()\n    stash = make([]byte, len(src))\n    copy(stash, src) // own the copy; the buffer may be freed safely\n    return nil\n}","handlingStrategy":"validation","validationCode":"// Copy any bytes you must retain past the buffer's lifetime.\nfunc retainData(b mem.Buffer) []byte {\n    src := b.ReadOnlyData()\n    out := make([]byte, len(src))\n    copy(out, src)\n    return out // safe after b.Free()\n}","typeGuard":"// Buffer exposes no 'freed' flag; track lifetime explicitly in your own code.\ntype ownedBuffer struct {\n    b    mem.Buffer\n    live bool\n}\nfunc (o *ownedBuffer) read() []byte {\n    if !o.live {\n        return nil // already freed\n    }\n    return o.b.ReadOnlyData()\n}","tryCatchPattern":"// Use-after-free panics must not be caught in production; recover only for diagnostics.\ndefer func() {\n    if r := recover(); r != nil {\n        log.Printf(\"buffer use-after-free: %v\", r)\n    }\n}()\n_ = b.ReadOnlyData()","preventionTips":["Treat Free() as the end of a buffer's lifetime; never read after it.","In CodecV2.Unmarshal, copy bytes you need to keep (the buffer is freed on return).","Ref() before sharing across goroutines; each owner frees its own reference."],"tags":["mem","buffer","use-after-free","panic","concurrency","go"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}