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

  1. Call Ref() to take your own reference before sharing or retaining a buffer; call Free() on your reference when done.
  2. Inside CodecV2.Unmarshal, copy any bytes you must keep past the return (the docs explicitly require this).
  3. 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

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


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/d51935db2d3f7287. Report an issue: GitHub.