Go version

go version go1.24.4 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='cc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='c++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/fxrlv/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/fxrlv/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/mg/1l0261p97kzflrmx68mm4z7c0000gn/T/go-build2006285917=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/tmp/checkptr/go.mod'
GOMODCACHE='/Users/fxrlv/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/fxrlv/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.24.4/libexec'
GOSUMDB='sum.golang.org'
GOTELEMETRY='on'
GOTELEMETRYDIR='/Users/fxrlv/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.24.4/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.4'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

There is an inconsistency in how the checkptr runtime check validates pointer arithmetic.

The issue is that checkptr validates manual pointer arithmetic using uintptr conversions but does not validate the unsafe.Add function, which can perform equivalent potentially unsafe operations.

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    data := make([]byte, 10)

    // This triggers checkptr validation and can panic with:
    // "fatal error: checkptr: pointer arithmetic result points to invalid allocation"
    manualArithmetic := func(offset int) *byte {
        return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(unsafe.SliceData(data))) + uintptr(offset)))
    }

    // This does NOT trigger checkptr validation, even with equivalent dangerous operations
    unsafeAdd := func(offset int) *byte {
        return (*byte)(unsafe.Add(unsafe.Pointer(unsafe.SliceData(data)), offset))
    }

    // Both can be equally dangerous when offset >= len(data) or offset < 0
    a := unsafeAdd(10)        // No checkptr validation
    b := manualArithmetic(10) // May trigger checkptr panic

    fmt.Println(a)
    fmt.Println(b)
}
$ go run -race main.go
fatal error: checkptr: pointer arithmetic result points to invalid allocation

goroutine 1 gp=0xc0000021c0 m=0 mp=0x100c056e0 [running]:
runtime.throw({0x100b0ac29?, 0x100b3e520?})
        /opt/homebrew/Cellar/go/1.24.4/libexec/src/runtime/panic.go:1101 +0x38 fp=0xc000187ea0 sp=0xc000187e70 pc=0x100ab5b68
runtime.checkptrArithmetic(0x100a56ec0?, {0xc000187f30, 0x1, 0x0?})
        /opt/homebrew/Cellar/go/1.24.4/libexec/src/runtime/checkptr.go:69 +0xa8 fp=0xc000187ed0 sp=0xc000187ea0 pc=0x100a58238
main.main.func1(...)
        /tmp/checkptr/main.go:14
main.main()
        /tmp/checkptr/main.go:24 +0x70 fp=0xc000187f40 sp=0xc000187ed0 pc=0x100b01550
runtime.main()
        /opt/homebrew/Cellar/go/1.24.4/libexec/src/runtime/proc.go:283 +0x284 fp=0xc000187fd0 sp=0xc000187f40 pc=0x100a84ef4
runtime.goexit({})
        /opt/homebrew/Cellar/go/1.24.4/libexec/src/runtime/asm_arm64.s:1223 +0x4 fp=0xc000187fd0 sp=0xc000187fd0 pc=0x100abc744

What did you see happen?

  • unsafe.Add calls: No validation, no panics
  • Manual pointer arithmetic: checkptr validates and panics with "pointer arithmetic result points to invalid allocation"

What did you expect to see?

Both manual pointer arithmetic and unsafe.Add should receive the same level of validation from checkptr. If manual uintptr arithmetic is checked for validity, then unsafe.Add calls should also be validated.

Comment From: gabyhelp

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: zigo101

Isn't it just the purpose of unsafe.Add? (to avoid checkptr)

Comment From: zigo101

Aha, it looks the effect of -race is different from -gcflags=all=-d=checkptr. The latter option doesn't report errors for both ways.

Sorry, my last comment is irrelevant.

Comment From: randall77

I agree it looks like checkptr instrumentation is not added for unsafe.Add when using the compiler option -d=checkptr. I believe we should fix that.

Comment From: thepudds

FYI, I've started to take a look at this and will likely send a CL.

Comment From: cuonglm

@thepudds I can send a fix if you haven't started yet.

Comment From: thepudds

Hi @cuonglm, I already have something that I think is close to working. I'll add you to the CL in case you have feedback. (And if I get stuck or seem like I won't have time, I'll circle back here to let you know). Thanks!

Comment From: cuonglm

Hi @cuonglm, I already have something that I think is close to working. I'll add you to the CL in case you have feedback. (And if I get stuck or seem like I won't have time, I'll circle back here to let you know). Thanks!

Yeah, I mean I have a working one already, but feel free to work on it if you want.

Comment From: gopherbot

Change https://go.dev/cl/690075 mentions this issue: cmd/compile/internal/walk: instrument unsafe.Add for checkptr runtime pointer validation

Comment From: thepudds

(Last week, @cuonglm and I chatted briefly elsewhere, and the conclusion was I am going to try move my version forward).

Comment From: thepudds

Hi @fxrlv, if you are interested, you could try out https://go.dev/cl/690075, such as via the official gotip utility:

$ go install golang.org/dl/gotip@latest    # install gotip
$ gotip download 690075                    # download and build CL 690075
$ gotip run -race main.go                  # use gotip as your normal 'go' command

Running on your original example, I now see:

fatal error: checkptr: pointer arithmetic result points to invalid allocation

goroutine 1 gp=0xc000002380 m=0 mp=0x5f00a0 [running]:
runtime.throw({0x51a629?, 0xa?})
        /home/.../sdk/gotip/src/runtime/panic.go:1094 +0x48 fp=0xc00007bea8 sp=0xc00007be78 pc=0x4a7928
runtime.checkptrArithmetic(0x4766f1?, {0xc00007bf38, 0x1, 0x0?})
        /home/.../sdk/gotip/src/runtime/checkptr.go:69 +0x9c fp=0xc00007bed8 sp=0xc00007bea8 pc=0x446a7c
main.main.func2(...)
        /home/.../work/scratch/checkptr-unsafe-add-74431/main.go:19
main.main()
        /home/.../work/scratch/checkptr-unsafe-add-74431/main.go:23 +0x73 fp=0xc00007bf50 sp=0xc00007bed8 pc=0x4eaa33
runtime.main()
[...]

The lines on main.go:19 and main.go:23 cited in the fatal error there correspond to the unsafe.Add in your original example. (In go1.24, it makes it to line main.go:24, which is past the unsafe.Add in your original example).

Comment From: gopherbot

Change https://go.dev/cl/692015 mentions this issue: cmd/compile, runtime: add checkptr instrumentation for unsafe.Add