golang / golang/go

runtime: AddCleanup is not robust to arg values pointing to struct embedded data

Open
#72,001 7 comments 1 reaction 2 assignees Claimed by @mknyszek View on GitHub
BugReport compiler/runtime Documentation NeedsFix
Dominant language
Go
Stars
139k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

### Go version

go version go1.25-20250216-RC00 cl/727547642 +d524e1eccd X:fieldtrack,boringcrypto linux/amd64

### Output of `go env` in your module/workspace:

```shell
AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/usr/local/google/home/mtp/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/usr/local/google/home/mtp/.config/go/env'
GOEXE=''
GOEXPERIMENT='fieldtrack,boringcrypto'
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3976460735=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/usr/local/google/home/mtp/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/usr/local/google/home/mtp/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/lib/google-golang'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/usr/local/google/home/mtp/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/lib/google-golang/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.25-20250216-RC00 cl/727547642 +d524e1eccd X:fieldtrack,boringcrypto'
GOWORK=''
PKG_CONFIG='pkg-config'
```

### What did you do?

I noticed that [`runtime.AddCleanup`](https://pkg.go.dev/runtime#AddCleanup) does not robustly handle a case like this:

```go
type NeedsCleanup struct { ... }
func (c *NeedsCleanup) Close() { ... }

type Embeds struct { NeedsCleanup }

func New() *Embeds {
var e Embeds
runtime.AddCleanup(&e, (*NeedsCleanup).Close, &e.NeedsCleanup)
...
return &e
}
```

### What did you see happen?

The runtime panics:

> panic: runtime.AddCleanup: ptr is equal to arg, cleanup will never run

### What did you expect to see?

I would expect that this [snippet of code](https://github.com/golang/go/blob/01ba8bfe868df2eea10ea8dd5bfbe5af0549909d/src/runtime/mcleanup.go#L79-L84) to be more robust to the case of struct embedding (if that is at all possible), or the documentation for `runtime.AddFunc` also mention that naive struct embedding of the data that needs to be cleaned up into an outer struct won't work due to [how alignment of memory works](https://dave.cheney.net/tag/alignment).

We found a workaround by amending the definition of the outer type as follows:

```go
type NeedsCleanup struct { ... }
func (c *NeedsCleanup) Close() { ... }

type Embeds struct {
_ int // Ensures that NeedsCleanup's address does not align with Embeds for finalization.
NeedsCleanup
}

func New() *Embeds {
var e Embeds
runtime.AddCleanup(&e, (*NeedsCleanup).Close, &e.NeedsCleanup)
...
return &e
}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.