grpc/grpc-go · critical

Cannot split freed buffer

Error message

Cannot split freed buffer

What it means

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.

Source

Thrown at mem/buffers.go:215

	if len(data) == len(b.data) {
		b.Ref()
		return b
	}
	// We are creating a new reference (view) to a portion of the root buffer's
	// data. Therefore, we must increment the reference count of the root buffer
	// to ensure the underlying data is not freed while this view is still in
	// use.
	b.rootBuf.Ref()
	s := newBuffer()
	s.data = data
	s.rootBuf = b.rootBuf
	s.refs.Store(1)
	return s
}

func (b *buffer) split(n int) (Buffer, Buffer) {
	if b.rootBuf == nil || b.rootBuf.refs.Add(1) <= 1 {
		panic("Cannot split freed buffer")
	}

	split := newBuffer()
	split.data = b.data[n:]
	split.rootBuf = b.rootBuf
	split.refs.Store(1)

	b.data = b.data[:n]

	return b, split
}

func (b *buffer) read(buf []byte) (int, Buffer) {
	if b.rootBuf == nil {
		panic("Cannot read freed buffer")
	}

	n := copy(buf, b.data)

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Call SplitUnsafe only on buffers known to be alive; Ref() first if lifetime is uncertain.
  2. Free each of the two returned references exactly once when done.
  3. Avoid splitting input buffers after the API that owns them has returned.

Example fix

// before
b.Free()
left, right := mem.SplitUnsafe(b, 4) // panic: freed

// after
left, right := mem.SplitUnsafe(b, 4) // split while alive
b.Free() // b's ref consumed; left/right hold their own refs
// later: left.Free(); right.Free()
Defensive patterns

Strategy: validation

Validate before calling

// SplitUnsafe only on live buffers.
func safeSplit(b mem.Buffer, n int) (mem.Buffer, mem.Buffer) {
    if b.Len() == 0 || n < 0 || n > b.Len() {
        return nil, nil
    }
    return mem.SplitUnsafe(b, n)
}

Prevention

When it happens

Trigger: 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.

Common situations: Custom streaming codecs/interceptors that call SplitUnsafe on input buffers past their lifetime; reusing a freed buffer variable; refcount mis-management across goroutines.

Related errors


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