grpc/grpc-go · critical
Cannot read freed buffer
Error message
Cannot read freed buffer
What it means
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.
Source
Thrown at mem/buffers.go:144
//
// It acquires a []byte from the given pool and copies over the backing array
// of the given data. The []byte acquired from the pool is returned to the
// pool when all references to the returned Buffer are released.
func Copy(data []byte, pool BufferPool) Buffer {
if IsBelowBufferPoolingThreshold(len(data)) {
buf := make(SliceBuffer, len(data))
copy(buf, data)
return buf
}
buf := pool.Get(len(data))
copy(*buf, data)
return NewBuffer(buf, pool)
}
func (b *buffer) ReadOnlyData() []byte {
if b.rootBuf == nil {
panic("Cannot read freed buffer")
}
return b.data
}
func (b *buffer) Ref() {
if b.refs.Add(1) <= 1 {
panic("Cannot ref freed buffer")
}
}
func (b *buffer) Free() {
refs := b.refs.Add(-1)
if refs < 0 {
panic("Cannot free freed buffer")
}
if refs > 0 {
return
}View on GitHub (pinned to 0c51461d27)
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.
Example fix
// before (custom CodecV2.Unmarshal keeps a slice past return -> freed)
var stash []byte
func (c myCodec) Unmarshal(data mem.BufferSlice, v any) error {
stash = data[0].ReadOnlyData() // freed after return -> later panic
return nil
}
// after
func (c myCodec) Unmarshal(data mem.BufferSlice, v any) error {
src := data[0].ReadOnlyData()
stash = make([]byte, len(src))
copy(stash, src) // own the copy; the buffer may be freed safely
return nil
} Defensive patterns
Strategy: validation
Validate before calling
// Copy any bytes you must retain past the buffer's lifetime.
func retainData(b mem.Buffer) []byte {
src := b.ReadOnlyData()
out := make([]byte, len(src))
copy(out, src)
return out // safe after b.Free()
} Type guard
// Buffer exposes no 'freed' flag; track lifetime explicitly in your own code.
type ownedBuffer struct {
b mem.Buffer
live bool
}
func (o *ownedBuffer) read() []byte {
if !o.live {
return nil // already freed
}
return o.b.ReadOnlyData()
} Try / catch
// Use-after-free panics must not be caught in production; recover only for diagnostics.
defer func() {
if r := recover(); r != nil {
log.Printf("buffer use-after-free: %v", r)
}
}()
_ = b.ReadOnlyData() Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cannot ref freed buffer
- Cannot free freed buffer
- Cannot slice freed buffer
- Cannot split freed buffer
- slice bounds out of range [%d:%d] with length 0
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/d51935db2d3f7287.
Report an issue: GitHub.