{"record":{"id":"6049d2c6bffeffd5","repo":"grpc/grpc-go","slug":"cannot-ref-freed-buffer","errorCode":null,"errorMessage":"Cannot ref freed buffer","messagePattern":"Cannot ref freed buffer","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"mem/buffers.go","lineNumber":151,"sourceCode":"\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}\n\n\tb.data = nil\n\tif b.rootBuf == b {\n\t\t// This buffer is the owner of the data slice and its ref count reached\n\t\t// 0, free the slice.\n\t\tif b.pool != nil {\n\t\t\tb.pool.Put(b.origData)","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/mem/buffers.go#L133-L169","documentation":"buffer.Ref() (mem/buffers.go:149) increments the atomic refcount and panics at line 150-152 if the new count is <= 1, meaning the prior count was <= 0 (already freed). You cannot take a new reference to a buffer whose lifetime has ended; doing so would resurrect pooled memory. Each goroutine that wants to use a buffer must Ref() it while it is still alive.","triggerScenarios":"Calling buf.Ref() after buf.Free() has already dropped the count to 0; ref-ing a buffer obtained from a path that already freed it; concurrent code where one goroutine freed the buffer before another called Ref().","commonSituations":"Sharing a Buffer across goroutines without Ref()-before-send; stashing a buffer for later use and the original owner already freed it; off-by-one in manual refcount management in a custom codec or interceptor.","solutions":["Ref() the buffer at the point you receive it (while it is guaranteed alive), before storing or sending it.","Pair every Ref() with a later Free() in the same scope that owns the reference.","Never assume a buffer received from an API is still alive after that API returns; take your own reference up front."],"exampleFix":"// before\nch <- buf      // send raw buffer\ngo func() {\n    b := <-ch\n    b.Ref()       // panic: already freed by sender\n}()\nbuf.Free()\n\n// after\nbuf.Ref()       // take ref while alive\nch <- buf\ngo func() {\n    b := <-ch\n    defer b.Free()\n    _ = b.ReadOnlyData()\n}()\nbuf.Free()","handlingStrategy":"validation","validationCode":"// Ref() at the point of receipt, while the buffer is guaranteed alive.\nfunc handOff(b mem.Buffer) mem.Buffer {\n    b.Ref() // take a new reference before giving it away\n    return b\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ref() buffers while they are alive, before storing or sending them.","Pair each Ref() with exactly one Free() in the owning scope.","Never assume a buffer is alive after the API that produced it returns."],"tags":["mem","buffer","use-after-free","refcount","panic","concurrency","go"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}