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
- 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.
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
- Ref() before splitting if lifetime is uncertain.
- Free each returned reference exactly once.
- Never split buffers after the owning API has returned.
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
- Cannot read freed buffer
- Cannot ref freed buffer
- Cannot slice freed buffer
- Cannot free 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/476dee24b4b01e81.
Report an issue: GitHub.