{"record":{"id":"1bf7c46a1722ed03","repo":"grpc/grpc-go","slug":"cannot-free-freed-buffer","errorCode":null,"errorMessage":"Cannot free freed buffer","messagePattern":"Cannot free freed buffer","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"mem/buffers.go","lineNumber":158,"sourceCode":"}\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)\n\t\t\tb.pool = nil\n\t\t}\n\t\tb.origData = nil\n\t} else {\n\t\t// This buffer doesn't own the data slice, decrement a ref on the root\n\t\t// buffer.\n\t\tb.rootBuf.Free()","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/mem/buffers.go#L140-L176","documentation":"buffer.Free() (mem/buffers.go:155) atomically decrements the refcount and panics at line 157-159 if the result is negative - i.e. Free() was called more times than there are references. This is a double-free guard: without it the same backing slice would be returned to the BufferPool twice and handed to two unrelated callers, corrupting data.","triggerScenarios":"Calling Free() twice on the same reference; two goroutines both Free()-ing a buffer that only one of them owned (forgot to Ref()); a cleanup path (defer) that frees plus an explicit Free() on the same reference.","commonSituations":"A defer b.Free() plus an explicit b.Free() in the same function; passing a buffer to a consumer that frees it while the producer also frees it; refcount bookkeeping bugs in custom codecs/interceptors.","solutions":["Each owner frees exactly the references it created/obtained via Ref(); never free a reference you did not take.","Avoid mixing `defer b.Free()` with an in-function `b.Free()`; pick one owner of each reference.","When handing a buffer to another goroutine, Ref() first and let the receiver own (and Free) that new reference."],"exampleFix":"// before (double free)\nfunc process(b mem.Buffer) {\n    defer b.Free()\n    if cond {\n        b.Free() // second free -> panic\n        return\n    }\n}\n\n// after\nfunc process(b mem.Buffer) {\n    defer b.Free() // single owner\n    if cond {\n        return\n    }\n}","handlingStrategy":"validation","validationCode":"// Give each reference a single, clear owner that frees it exactly once.\ntype ref struct {\n    b    mem.Buffer\n    done bool\n}\nfunc (r *ref) free() {\n    if r.done {\n        log.Print(\"double free avoided\")\n        return\n    }\n    r.done = true\n    r.b.Free()\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Free only references you created via Ref() or construction.","Do not mix `defer b.Free()` with an explicit `b.Free()` on the same reference.","When sending a buffer to another goroutine, Ref() first and let the receiver own that reference."],"tags":["mem","buffer","double-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"}