Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: SIGBUS in initItab (write to readonly ITab.Fun) on unchecked type assertion with generics #65962

Closed
Meroje opened this issue Feb 27, 2024 · 4 comments
Assignees
Labels
compiler/runtime Issues related to the Go compiler and/or runtime.
Milestone

Comments

@Meroje
Copy link

Meroje commented Feb 27, 2024

Go version

from go1.18 to gotip(go1.23-ccbc725f)

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/meroje/Library/Caches/go-build'
GOENV='/Users/meroje/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/meroje/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/meroje/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.22.0/libexec'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.22.0/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.22.0'
GCCGO='gccgo'
AR='ar'
CC='cc'
CXX='c++'
CGO_ENABLED='1'
GOMOD='/Users/meroje/projects/memcrash/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/fq/_z724rr10kb802r6v4m4jnjw0000gq/T/go-build1130271614=/tmp/go-build -gno-record-gcc-switches -fno-common'

What did you do?

This came up during an internal code review on a convoluted dynamodb unmarshaler implementation, the crash could be striped down to this:

https://go.dev/play/p/buw65E2QFzU

package main

import "fmt"

type I interface {
	x()
}

func check[T bool]() {
	var value bool
	if v, ok := any(value).(I); ok {
		_ = v.(T)
		panic("unreachable")
	}
	_ = any(value).(I)
}

func main() {
	defer func() {
		err := recover()
		fmt.Printf("recovered: %v\n", err)
	}()
	check()
}

The crash only occurs if the v.(T) is present, even though the condition evaluates to false (shown by not reaching the unreachable panic). Checking any of the 2 assertions also prevents the crash.

What did you see happen?

unexpected fault address 0x1004545c0
fatal error: fault
[signal SIGBUS: bus error code=0x1 addr=0x1004545c0 pc=0x10038d328]

goroutine 1 gp=0x140000021c0 m=0 mp=0x1004ca540 [running]:
runtime.throw({0x1004127ad?, 0x10038d168?})
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/panic.go:1023 +0x40 fp=0x1400010ad40 sp=0x1400010ad10 pc=0x1003b6140
runtime.sigpanic()
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/signal_unix.go:878 +0x178 fp=0x1400010ada0 sp=0x1400010ad40 pc=0x1003cdd38
runtime.(*itab).init(0x1004545a8)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/iface.go:239 +0x338 fp=0x1400010ae70 sp=0x1400010adb0 pc=0x10038d328
runtime.getitab(0x100443da0, 0x10043ff20, 0x0)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/iface.go:93 +0x17c fp=0x1400010aec0 sp=0x1400010ae70 pc=0x10038cc9c
runtime.typeAssert(0x1004c5bf0, 0x10043ff20)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/iface.go:434 +0x44 fp=0x1400010af00 sp=0x1400010aec0 pc=0x10038d994
main.check[...](...)
	/Users/meroje/projects/memcrash/main.go:15
main.main()
	/Users/meroje/projects/memcrash/main.go:23 +0xe8 fp=0x1400010af40 sp=0x1400010af00 pc=0x1004121e8
runtime.main()
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/proc.go:271 +0x28c fp=0x1400010afd0 sp=0x1400010af40 pc=0x1003b8a7c
runtime.goexit({})
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/asm_arm64.s:1222 +0x4 fp=0x1400010afd0 sp=0x1400010afd0 pc=0x1003e7754

goroutine 2 gp=0x14000002700 m=nil [force gc (idle)]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/proc.go:402 +0xc8 fp=0x14000058f90 sp=0x14000058f70 pc=0x1003b8ea8
runtime.goparkunlock(...)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/proc.go:408
runtime.forcegchelper()
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/proc.go:326 +0xb8 fp=0x14000058fd0 sp=0x14000058f90 pc=0x1003b8d38
runtime.goexit({})
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/asm_arm64.s:1222 +0x4 fp=0x14000058fd0 sp=0x14000058fd0 pc=0x1003e7754
created by runtime.init.6 in goroutine 1
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/proc.go:314 +0x24

goroutine 3 gp=0x14000002c40 m=nil [GC sweep wait]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/proc.go:402 +0xc8 fp=0x14000059760 sp=0x14000059740 pc=0x1003b8ea8
runtime.goparkunlock(...)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/proc.go:408
runtime.bgsweep(0x14000072000)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/mgcsweep.go:278 +0xa0 fp=0x140000597b0 sp=0x14000059760 pc=0x1003a5ab0
runtime.gcenable.gowrap1()
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/mgc.go:203 +0x28 fp=0x140000597d0 sp=0x140000597b0 pc=0x100399f48
runtime.goexit({})
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000597d0 sp=0x140000597d0 pc=0x1003e7754
created by runtime.gcenable in goroutine 1
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/mgc.go:203 +0x6c

goroutine 17 gp=0x14000084380 m=nil [GC scavenge wait]:
runtime.gopark(0x14000072000?, 0x100433cd0?, 0x1?, 0x0?, 0x14000084380?)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/proc.go:402 +0xc8 fp=0x14000054760 sp=0x14000054740 pc=0x1003b8ea8
runtime.goparkunlock(...)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/proc.go:408
runtime.(*scavengerState).park(0x1004c9e60)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/mgcscavenge.go:425 +0x5c fp=0x14000054790 sp=0x14000054760 pc=0x1003a34ac
runtime.bgscavenge(0x14000072000)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/mgcscavenge.go:653 +0x44 fp=0x140000547b0 sp=0x14000054790 pc=0x1003a3a04
runtime.gcenable.gowrap2()
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/mgc.go:204 +0x28 fp=0x140000547d0 sp=0x140000547b0 pc=0x100399ee8
runtime.goexit({})
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000547d0 sp=0x140000547d0 pc=0x1003e7754
created by runtime.gcenable in goroutine 1
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/mgc.go:204 +0xac

goroutine 33 gp=0x1400011e000 m=nil [finalizer wait]:
runtime.gopark(0x0?, 0x0?, 0x6d?, 0x0?, 0x1003976b4?)
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/proc.go:402 +0xc8 fp=0x14000058580 sp=0x14000058560 pc=0x1003b8ea8
runtime.runfinq()
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/mfinal.go:194 +0x108 fp=0x140000587d0 sp=0x14000058580 pc=0x100399018
runtime.goexit({})
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000587d0 sp=0x140000587d0 pc=0x1003e7754
created by runtime.createfing in goroutine 1
	/opt/homebrew/Cellar/go/1.22.0/libexec/src/runtime/mfinal.go:164 +0x80
exit status 2

What did you expect to see?

We expected to recover from a common type assertion panic, as is the case without the unreachable if branch.

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Feb 27, 2024
@shavounet
Copy link

While step-debugging, I saw the segmentation fault occurring at this line (which can be seen in above stack trace)

m.fun[0] = 0

@adonovan
Copy link
Member

The value of the indirect store register x2 is a readonly symbol:

->  0x1000093d8 <+824>: str    xzr, [x2, #0x18]

x2 = 0x00000001000d48e8  x`go:itab.bool,main.I

nm: 1000d48e8 R go:itab.bool,main.I

@adonovan adonovan changed the title runtime: "SIGBUS: bus error code=0x1" on unchecked type assertion with generics runtime: SIGBUS in initItab (write to readonly ITab.Fun) on unchecked type assertion with generics Feb 27, 2024
@randall77
Copy link
Contributor

The runtime is trying to (re-)initialize an itab that lives in read-only memory. It can only happen when initializing an itab that represents the "type does not implement interface" condition, which in turn can only happen in certain generics scenarios.
We're reinitializing the itab just for the side-effect of finding a missing method name to report. That seems unnecessary.

Simpler repro:

package main

type I interface {
	foo()
}

type B bool

//go:noinline
func f[T any]() {
	var x I
	if _, ok := x.(T); ok {
	}
}

func isI(x any) {
	_ = x.(I)
}

func main() {
	f[B]() // add <B,I> itab to binary
	isI(B(false))
}

In particular, generics is used just to load the offending itab into read-only memory. Once it is there, no generics is needed to trigger the bug.

@randall77 randall77 self-assigned this Feb 27, 2024
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/567615 mentions this issue: runtime: don't re-initialize itab while looking for missing function

@mknyszek mknyszek added this to the Go1.23 milestone Feb 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime.
Projects
Development

No branches or pull requests

6 participants