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

Cannot recover from nil pointer dereference on Windows when Control Flow Guard is enabled #53560

Closed
amit13k opened this issue Jun 27, 2022 · 8 comments
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Windows
Milestone

Comments

@amit13k
Copy link

amit13k commented Jun 27, 2022

What version of Go are you using (go version)?

$ go version
go version go1.18.3 windows/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env

set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\Amit\AppData\Local\go-build
set GOENV=C:\Users\Amit\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\Amit\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\Amit\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.18.3
set GCCGO=gccgo
set GOAMD64=v1
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=NUL
set GOWORK=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\Amit\AppData\Local\Temp\go-build3859288877=/tmp/go-build -gno-record-gcc-switches

What did you do?

Tried to run this

package main

import "fmt"

type TestStruct struct {
	x int
}

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("caught error", err)
		}
	}()

	var x *TestStruct = nil
	fmt.Println(x.x)
}

with go run main.go command

What did you expect to see?

I expected the output to be,
caught error runtime error: invalid memory address or nil pointer dereference

What did you see instead?

The output was,

exit status 0xc0000409

It works if I disable the CFG(Control Flow Guard) feature under windows exploit protection settings or add the generated binary as an exception for CFG.

I encountered this same problem when running another golang project, grpcui, on windows https://github.com/fullstorydev/grpcui. The problem there is also fixed by disabled CFG.

I wonder if this is an issue/bug with how the golang compiler produces the binary for windows.

Always asking end-users to manually add a CFG exception for golang generated binaries doesn't seem right. (Maybe it's correct if the issue is indeed on the Windows end).

@qmuntal
Copy link
Contributor

qmuntal commented Jun 27, 2022

Strange. Go does not generate CFG-enabled binaries (see #35940) nor I can reproduce your issue with Windows 11 10.0.22000. Which Windows version are you using?

@amit13k
Copy link
Author

amit13k commented Jun 27, 2022

I noticed some weird things when trying to reproduce the issue. On some other windows machine, I had to change the Exploit Protection -> Control Flow Guard to "On by default" and restart to make this issue happen. For strange reasons, "Use default(on)" didn't lead to any problem. Also, I am on Windows 11 Insider Preview 10.0.25145.1011.

@seankhliao seankhliao added WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. OS-Windows labels Jun 28, 2022
@qmuntal
Copy link
Contributor

qmuntal commented Jun 28, 2022

I can confirm that switching CFG to "On by default" makes the program crash. No clue on what the cause could be.

@gopherbot
Copy link

Timed out in state WaitingForInfo. Closing.

(I am just a bot, though. Please speak up if this is a mistake or you have the requested information.)

@qmuntal
Copy link
Contributor

qmuntal commented Sep 28, 2022

This issue report is valid and should be reopened. I don't think we need more info, just investigation.

@ianlancetaylor ianlancetaylor added NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. and removed WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. labels Sep 29, 2022
@ianlancetaylor ianlancetaylor added this to the Backlog milestone Sep 29, 2022
@qmuntal
Copy link
Contributor

qmuntal commented Sep 29, 2022

This is what I get from WinDbg:

0:000> g
WARNING: Continuing a non-continuable exception
(7ae4.5248): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!)
Subcode: 0xd FAST_FAIL_INVALID_SET_OF_CONTEXT 
WARNING: Stack overflow detected. The unwound frames are extracted from outside normal stack bounds.
ntdll!RtlGuardRestoreContext+0x65:
00007ffe`315fda75 cd29            int     29h
WARNING: Stack overflow detected. The unwound frames are extracted from outside normal stack bounds.

I can see that sigtramp and exceptionhandler are being called, so Go runtime tries to handle the exception, yet the context stack pointer is not within the system stack bounds after handling the exception.

sigtramp implementation for windows/arm and windows/arm64 contain some complex logic the set up a control flow guard workaround:

// Check if we need to set up the control flow guard workaround.
// On Windows, the stack pointer in the context must lie within
// system stack limits when we resume from exception.
// Store the resume SP and PC on the g0 stack,
// and return to sigresume on the g0 stack. sigresume
// pops the saved PC and SP from the g0 stack, resuming execution
// at the desired location.
// If sigresume has already been set up by a previous exception
// handler, don't clobber the stored SP and PC on the stack.

I've tried porting the workaround to windows/amd64, but my assembly skills are not that good yet.

@zx2c4 @aclements

@gopherbot
Copy link

Change https://go.dev/cl/437559 mentions this issue: runtime: support control flow guard on windows/amd64

@qmuntal
Copy link
Contributor

qmuntal commented Oct 2, 2022

I've finally gave it a try in CL 437559.

romaindoumenc pushed a commit to TroutSoftware/go that referenced this issue Nov 3, 2022
The stack pointer must lie within system stack limits
when Control Flow Guard (CFG) is enabled on Windows.

This CL updates runtime.sigtramp to honor this restriction by
porting some code from the windows/arm64 version, which
already supports CFG.

Fixes golang#53560

Change-Id: I7f88f9ae788b2bac38aac898b2567f1bea62f8f3
Reviewed-on: https://go-review.googlesource.com/c/go/+/437559
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
@golang golang locked and limited conversation to collaborators Nov 3, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Windows
Projects
None yet
Development

No branches or pull requests

5 participants