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.