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

syscall: (*LazyProc).Call does not keep arguments alive (anymore) #34474

Closed
eliasnaur opened this issue Sep 23, 2019 · 29 comments
Closed

syscall: (*LazyProc).Call does not keep arguments alive (anymore) #34474

eliasnaur opened this issue Sep 23, 2019 · 29 comments

Comments

@eliasnaur
Copy link
Contributor

@eliasnaur eliasnaur commented Sep 23, 2019

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

$ go version
go version go1.13 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\elias\AppData\Local\go-build
set GOENV=C:\Users\elias\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\elias\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=c:\go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=c:\go\pkg\tool\windows_amd64
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
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\elias\AppData\Local\Temp\go-build579976134=/tmp/go-build -gno-record-gcc-switches

What did you do?

On my Windows 10, run the program from https://play.golang.org/p/2JzHDalGN7Q.

What did you expect to see?

No errors.

What did you see instead?

$ go run .
panic: finalized message before returning from window proc!
goroutine 1 [running, locked to thread]:
main.windowProc(0xb04c8, 0x31f, 0x1, 0x0, 0x7bfc48)
        C:/Users/elias/syscallcrash/crash.go:157 +0x186
syscall.Syscall(0x7ffec53f6020, 0x1, 0xc000044000, 0x0, 0x0, 0x0, 0x0, 0x0)
        c:/go/src/runtime/syscall_windows.go:184 +0xfa
syscall.(*Proc).Call(0xc0000045c0, 0xc000094028, 0x1, 0x1, 0x8, 0x4becc0, 0x4b7001, 0xc000094028)
        c:/go/src/syscall/dll_windows.go:171 +0x136
syscall.(*LazyProc).Call(0xc00006c390, 0xc000094028, 0x1, 0x1, 0x4, 0x4, 0x1, 0x0)
        c:/go/src/syscall/dll_windows.go:328 +0x66
main.dispatchMessage(0xc000044000)
        C:/Users/elias/syscallcrash/crash.go:207 +0xde
main.CreateWindow(0x1, 0xc000056058)
        C:/Users/elias/syscallcrash/crash.go:87 +0x128
main.main()
        C:/Users/elias/syscallcrash/crash.go:72 +0x29
exit status 2

Which means that the m *msg argument to dispatchMessage was finalized before the call to the window procedure callback returned, indicating that the _DispatchMessage.Call didn't keep its argument alive.

On my machine, I can comment out the panic and get a native crash instead:

$ go run .
Exception 0xc0000005 0x1 0xc000044010 0x7ffec53f622d
PC=0x7ffec53f622d

syscall.Syscall(0x7ffec53f6020, 0x1, 0xc000044000, 0x0, 0x0, 0x0, 0x0, 0x0)
        c:/go/src/runtime/syscall_windows.go:184 +0xfa
syscall.(*Proc).Call(0xc0000045c0, 0xc000010038, 0x1, 0x1, 0x8, 0x4becc0, 0x4b7001, 0xc000010038)
        c:/go/src/syscall/dll_windows.go:171 +0x136
syscall.(*LazyProc).Call(0xc00006c390, 0xc000010038, 0x1, 0x1, 0x4, 0x4, 0x1, 0x0)
        c:/go/src/syscall/dll_windows.go:328 +0x66
main.dispatchMessage(0xc000044000)
        C:/Users/elias/syscallcrash/crash.go:207 +0xde
main.CreateWindow(0x1, 0xc000056058)
        C:/Users/elias/syscallcrash/crash.go:87 +0x128
main.main()
        C:/Users/elias/syscallcrash/crash.go:72 +0x29

goroutine 6 [chan receive]:
main.init.0.func1()
        C:/Users/elias/syscallcrash/crash.go:126 +0x74
created by main.init.0
        C:/Users/elias/syscallcrash/crash.go:125 +0x3c
rax     0x0
rbx     0x25ed820
rcx     0x151040
rdi     0x45c640
rsi     0xc000044000
rbp     0xc000049cc0
rsp     0x7bfd90
r8      0x7bfc90
r9      0x7ffec089e2a8
r10     0x0
r11     0x7bfd60
r12     0x4cfe20
r13     0x0
r14     0x1
r15     0x0
rip     0x7ffec53f622d
rflags  0x10246
cs      0x33
fs      0x53
gs      0x2b
exit status 2

This issue seems like a variant of #16035. And sure enough, adding runtime.KeepAlive(m) to dispatchMessage does fix the native crash above.

@bcmills
Copy link
Member

@bcmills bcmills commented Sep 23, 2019

@bcmills bcmills added this to the Go1.14 milestone Sep 23, 2019
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Sep 24, 2019

CC @mdempsky

This is implemented via the magic comment //go:uintptrescapes, which the compiler implements in the escape analysis pass. I wonder if something has gone wrong with the recent rewrite of escape analysis.

@eliasnaur
Copy link
Contributor Author

@eliasnaur eliasnaur commented Sep 24, 2019

FWIW, below is the output of -gcflags=-m. The uintptr arguments do seem to be moved to the heap as they should.

EDIT: Unrelated to DispatchMessage, I don't see anything about wname from line 177 moving to the heap, but I think it should.

$ go build -gcflags=-m .
# _/C_/Users/elias/syscallcrash
.\crash.go:95:37: inlining call to syscall.NewCallback
.\crash.go:226:6: can inline showWindow
.\crash.go:210:6: can inline getMessage
.\crash.go:204:26: can inline dispatchMessage.func1
.\crash.go:84:12: inlining call to showWindow
.\crash.go:86:12: inlining call to getMessage
.\crash.go:71:6: can inline main
.\crash.go:195:6: can inline defWindowProc
.\crash.go:149:20: inlining call to debug.FreeOSMemory
.\crash.go:154:22: inlining call to defWindowProc
.\crash.go:163:6: can inline (*window).setAnimating
.\crash.go:218:22: leaking param: cls
.\crash.go:219:37: ... argument escapes to heap
.\crash.go:221:23: registerClassEx ... argument does not escape
.\crash.go:221:24: err escapes to heap
.\crash.go:176:59: createWindowEx lpWindowName does not escape
.\crash.go:178:38: ... argument escapes to heap
.\crash.go:190:23: createWindowEx ... argument does not escape
.\crash.go:190:24: err escapes to heap
.\crash.go:92:2: moved to heap: wcls
.\crash.go:95:37: windowProc escapes to heap
.\crash.go:202:22: leaking param: m
.\crash.go:204:31: dispatchMessage.func1 m does not escape
.\crash.go:203:13: dispatchMessage ... argument does not escape
.\crash.go:203:14: "Setting finalizer" escapes to heap
.\crash.go:204:22: m escapes to heap
.\crash.go:204:26: func literal escapes to heap
.\crash.go:204:26: func literal escapes to heap
.\crash.go:207:23: ... argument escapes to heap
.\crash.go:85:6: moved to heap: msg
.\crash.go:84:12: ... argument escapes to heap
.\crash.go:86:12: ... argument escapes to heap
.\crash.go:125:5: func literal escapes to heap
.\crash.go:150:9: Event literal escapes to heap
.\crash.go:154:22: ... argument escapes to heap
.\crash.go:163:7: (*window).setAnimating w does not escape
.\crash.go:196:32: ... argument escapes to heap
.\crash.go:210:17: leaking param: m
.\crash.go:211:29: ... argument escapes to heap
.\crash.go:227:18: ... argument escapes to heap
@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Sep 24, 2019

I could reproduce this problem on current tip. But I could not reproduce this problem on bbe5da4. So I bisected the problem to:

ca4089ad62b806db7d3f32335d3f20865a75edcd is the first bad commit
commit ca4089ad62b806db7d3f32335d3f20865a75edcd
Author: Keith Randall <khr@golang.org>
Date:   Wed Aug 31 15:17:02 2016 -0700

    cmd/compile: args no longer live until end-of-function
    
    We're dropping this behavior in favor of runtime.KeepAlive.
    Implement runtime.KeepAlive as an intrinsic.
    
    Update #15843
    
    Change-Id: Ib60225bd30d6770ece1c3c7d1339a06aa25b1cbc
    Reviewed-on: https://go-review.googlesource.com/28310
    Run-TryBot: Keith Randall <khr@golang.org>
    TryBot-Result: Gobot Gobot <gobot@golang.org>
    Reviewed-by: David Chase <drchase@google.com>

:040000 040000 0ecef6a9199c948b95c4ba1eb995f925e4fef99e 13b3faed419c7f9af4457d2cbb3e7aed8f9f338c M      src
:040000 040000 e2c1ebf68b227bc1dfc6fd2f07201cc9a4d672c9 af9c70dd32200508197075617f7ea908afae7929 M      test

Alex

@eliasnaur
Copy link
Contributor Author

@eliasnaur eliasnaur commented Sep 24, 2019

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Sep 24, 2019

@alexbrainman Are you sure that's the right bisect? That was three years ago. Seems like go:uinptrescapes has been working since then, as in, for example, #23045.

@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Sep 26, 2019

@alexbrainman Are you sure that's the right bisect?

That is what I did after I have found the culprit

c:\Users\Alex\dev\go\src>type %GOPATH%\src\issue\go\34474\main.go
package main

import (
        "fmt"
        "log"
        "runtime"
        "runtime/debug"
        "syscall"
        "time"
        "unsafe"
        //syscall "golang.org/x/sys/windows"
)

type wndClassEx struct {
        cbSize        uint32
        style         uint32
        lpfnWndProc   uintptr
        cnClsExtra    int32
        cbWndExtra    int32
        hInstance     syscall.Handle
        hIcon         syscall.Handle
        hCursor       syscall.Handle
        hbrBackground syscall.Handle
        lpszMenuName  *uint16
        lpszClassName *uint16
        hIconSm       syscall.Handle
}

type msg struct {
        hwnd     syscall.Handle
        message  uint32
        wParam   uintptr
        lParam   uintptr
        time     uint32
        pt       point
        lPrivate uint32
}

type point struct {
        x, y int32
}

type window struct {
        hwnd syscall.Handle
}

const (
        _CS_HREDRAW = 0x0002
        _CS_VREDRAW = 0x0001
        _CS_OWNDC   = 0x0020

        _CW_USEDEFAULT = -2147483648

        _SW_SHOWDEFAULT = 10

        _WS_CLIPCHILDREN     = 0x00010000
        _WS_CLIPSIBLINGS     = 0x04000000
        _WS_OVERLAPPED       = 0x00000000
        _WS_OVERLAPPEDWINDOW = _WS_OVERLAPPED | _WS_CAPTION | _WS_SYSMENU | _WS_THICKFRAME |
                _WS_MINIMIZEBOX | _WS_MAXIMIZEBOX
        _WS_CAPTION     = 0x00C00000
        _WS_SYSMENU     = 0x00080000
        _WS_THICKFRAME  = 0x00040000
        _WS_MINIMIZEBOX = 0x00020000
        _WS_MAXIMIZEBOX = 0x00010000

        _WS_EX_APPWINDOW  = 0x00040000
        _WS_EX_WINDOWEDGE = 0x00000100
)

func main() {
        if err := CreateWindow(); err != nil {
                panic(err)
        }
}

func CreateWindow() error {
        // Call win32 API from a single OS thread.
        runtime.LockOSThread()
        hwnd, err := createNativeWindow()
        if err != nil {
                return err
        }
        showWindow(hwnd, _SW_SHOWDEFAULT)
        var msg msg
        getMessage(&msg, hwnd, 0, 0)
        dispatchMessage(&msg)
        return nil
}

func createNativeWindow() (syscall.Handle, error) {
        wcls := wndClassEx{
                cbSize:        uint32(unsafe.Sizeof(wndClassEx{})),
                style:         _CS_HREDRAW | _CS_VREDRAW | _CS_OWNDC,
                lpfnWndProc:   syscall.NewCallback(windowProc),
                lpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
        }
        cls, err := registerClassEx(&wcls)
        if err != nil {
                return 0, err
        }
        dwStyle := uint32(_WS_OVERLAPPEDWINDOW)
        dwExStyle := uint32(_WS_EX_APPWINDOW | _WS_EX_WINDOWEDGE)
        hwnd, err := createWindowEx(dwExStyle,
                cls,
                "Test title",
                dwStyle|_WS_CLIPSIBLINGS|_WS_CLIPCHILDREN,
                _CW_USEDEFAULT, _CW_USEDEFAULT,
                800,
                600,
                0,
                0,
                0,
                0)
        if err != nil {
                return 0, err
        }
        return hwnd, nil
}

var chanIn = make(chan interface{})
var chanOut = make(chan struct{})

func init() {
        go func() {
                for range chanIn {
                        chanOut <- struct{}{}
                }
        }()
}

type Event struct {
        Type      uint8
        Source    uint8
        PointerID uint16
        Priority  uint8
        Time      time.Duration
        Hit       bool
        Position  Point
        Scroll    Point
}

type Point struct {
        X, Y float32
}

func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
        runtime.GC()
        debug.FreeOSMemory()
        chanIn <- Event{}
        <-chanOut
        switch msg {
        }
        ret := defWindowProc(hwnd, msg, wParam, lParam)
        select {
        case <-finalizerRan:
                panic("finalized message before returning from window proc!")
        default:
        }
        return ret
}

func (w *window) setAnimating(anim bool) {
}

var (
        user32            = syscall.NewLazyDLL("user32.dll")
        _CreateWindowEx   = user32.NewProc("CreateWindowExW")
        _DefWindowProc    = user32.NewProc("DefWindowProcW")
        _DispatchMessage  = user32.NewProc("DispatchMessageW")
        _GetMessage       = user32.NewProc("GetMessageW")
        _RegisterClassExW = user32.NewProc("RegisterClassExW")
        _ShowWindow       = user32.NewProc("ShowWindow")
)

func createWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, dwStyle uint32, x, y, w, h int32, hWndParent, hMenu, hInstance syscall.Handle, lpParam uintptr) (syscall.Handle, error) {
        wname := syscall.StringToUTF16Ptr(lpWindowName)
        hwnd, _, err := _CreateWindowEx.Call(
                uintptr(dwExStyle),
                uintptr(lpClassName),
                uintptr(unsafe.Pointer(wname)),
                uintptr(dwStyle),
                uintptr(x), uintptr(y),
                uintptr(w), uintptr(h),
                uintptr(hWndParent),
                uintptr(hMenu),
                uintptr(hInstance),
                uintptr(lpParam))
        if hwnd == 0 {
                return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
        }
        return syscall.Handle(hwnd), nil
}

func defWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
        r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
        return r
}

var finalizerRan = make(chan struct{}, 1)

func dispatchMessage(m *msg) {
        log.Println("Setting finalizer")
        runtime.SetFinalizer(m, func(m *msg) {
                finalizerRan <- struct{}{}
        })
        _DispatchMessage.Call(uintptr(unsafe.Pointer(m)))
}

func getMessage(m *msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax ui
nt32) int32 {
        r, _, _ := _GetMessage.Call(uintptr(unsafe.Pointer(m)),
                uintptr(hwnd),
                uintptr(wMsgFilterMin),
                uintptr(wMsgFilterMax))
        return int32(r)
}

func registerClassEx(cls *wndClassEx) (uint16, error) {
        a, _, err := _RegisterClassExW.Call(uintptr(unsafe.Pointer(cls)))
        if a == 0 {
                return 0, fmt.Errorf("RegisterClassExW failed: %v", err)
        }
        return uint16(a), nil
}

func showWindow(hwnd syscall.Handle, nCmdShow int32) {
        _ShowWindow.Call(uintptr(hwnd), uintptr(nCmdShow))
}

c:\Users\Alex\dev\go\src>git checkout ca4089a~
HEAD is now at faf611a07a... net/http: rename Post's parameter from bodyType to contentType

c:\Users\Alex\dev\go\src>git status
HEAD detached at faf611a07a
nothing to commit, working tree clean

c:\Users\Alex\dev\go\src>make
##### Building Go bootstrap tool.
cmd/dist

##### Building Go toolchain using c:\users\alex\dev\go1.4.3.
bootstrap/internal/dwarf
bootstrap/internal/sys
bootstrap/asm/internal/flags
bootstrap/internal/bio
bootstrap/compile/internal/big
bootstrap/compile/internal/syntax
bootstrap/internal/obj
bootstrap/internal/gcprog
bootstrap/internal/obj/arm
bootstrap/internal/obj/arm64
bootstrap/internal/obj/mips
bootstrap/internal/obj/ppc64
bootstrap/internal/obj/s390x
bootstrap/internal/obj/x86
bootstrap/asm/internal/lex
bootstrap/link/internal/ld
bootstrap/asm/internal/arch
bootstrap/compile/internal/ssa
bootstrap/asm/internal/asm
bootstrap/asm
bootstrap/link/internal/amd64
bootstrap/link/internal/arm
bootstrap/link/internal/arm64
bootstrap/link/internal/mips64
bootstrap/link/internal/ppc64
bootstrap/link/internal/s390x
bootstrap/link/internal/x86
bootstrap/link
bootstrap/compile/internal/gc
bootstrap/compile/internal/amd64
bootstrap/compile/internal/arm
bootstrap/compile/internal/arm64
bootstrap/compile/internal/mips64
bootstrap/compile/internal/ppc64
bootstrap/compile/internal/s390x
bootstrap/compile/internal/x86
bootstrap/compile

##### Building go_bootstrap for host, windows/amd64.
runtime/internal/sys
runtime/internal/atomic
runtime
encoding
errors
internal/race
internal/syscall/windows/sysdll
math
sort
sync/atomic
unicode
unicode/utf16
unicode/utf8
container/heap
sync
internal/singleflight
io
syscall
bytes
hash
strings
hash/adler32
bufio
strconv
path
internal/syscall/windows
internal/syscall/windows/registry
reflect
encoding/base64
crypto
regexp/syntax
time
crypto/sha1
os
regexp
os/signal
path/filepath
encoding/binary
fmt
io/ioutil
context
flag
go/token
log
net/url
text/template/parse
compress/flate
encoding/json
debug/dwarf
go/scanner
compress/zlib
os/exec
go/ast
text/template
debug/macho
debug/elf
go/parser
go/doc
go/build
cmd/go
        1 file(s) moved.

##### Building packages and commands for windows/amd64.
runtime/internal/sys
runtime/internal/atomic
runtime
errors
internal/race
sync/atomic
unicode
unicode/utf8
math
sync
internal/syscall/windows/sysdll
unicode/utf16
io
syscall
sort
bytes
strconv
strings
bufio
reflect
path
hash
regexp/syntax
hash/adler32
text/tabwriter
internal/syscall/windows
internal/syscall/windows/registry
hash/crc32
time
compress/bzip2
regexp
container/heap
container/list
container/ring
crypto
crypto/subtle
math/rand
crypto/cipher
os
crypto/sha512
crypto/aes
encoding/binary
crypto/hmac
crypto/md5
crypto/rc4
crypto/sha1
cmd/internal/sys
crypto/des
crypto/sha256
encoding/base64
fmt
path/filepath
encoding/pem
internal/nettrace
internal/singleflight
encoding
io/ioutil
encoding/ascii85
encoding/base32
hash/crc64
hash/fnv
html
image/color
index/suffixarray
cmd/internal/dwarf
flag
log
debug/dwarf
compress/flate
debug/gosym
cmd/internal/obj
debug/plan9obj
cmd/vendor/golang.org/x/arch/arm/armasm
compress/zlib
debug/macho
debug/elf
cmd/internal/goobj
debug/pe
cmd/vendor/golang.org/x/arch/x86/x86asm
archive/tar
archive/zip
compress/gzip
compress/lzw
context
math/big
encoding/hex
net
database/sql/driver
cmd/internal/objfile
database/sql
cmd/addr2line
encoding/csv
encoding/gob
crypto/dsa
crypto/elliptic
encoding/asn1
crypto/rand
crypto/rsa
crypto/ecdsa
crypto/x509/pkix
encoding/json
encoding/xml
vendor/golang_org/x/net/http2/hpack
crypto/x509
vendor/golang_org/x/net/idna
vendor/golang_org/x/net/lex/httplex
mime
mime/quotedprintable
net/textproto
net/http/httptrace
net/http/internal
crypto/tls
net/url
go/token
mime/multipart
go/scanner
text/template/parse
go/constant
go/ast
os/exec
text/template
text/scanner
go/parser
net/http
go/printer
go/doc
go/types
go/format
html/template
go/build
image
image/color/palette
runtime/debug
runtime/pprof
image/internal/imageutil
image/draw
image/jpeg
image/gif
image/png
runtime/trace
testing
internal/trace
go/internal/gccgoimporter
go/internal/gcimporter
expvar
internal/testenv
log/syslog
math/cmplx
go/importer
net/http/cgi
net/http/cookiejar
net/http/httptest
net/http/httputil
net/http/pprof
net/http/fcgi
net/internal/socktest
net/mail
net/rpc
net/smtp
os/signal
os/user
plugin
cmd/cgo
runtime/race
testing/iotest
testing/quick
cmd/api
net/rpc/jsonrpc
cmd/internal/obj/arm
cmd/internal/obj/arm64
cmd/internal/obj/mips
cmd/internal/obj/ppc64
cmd/internal/obj/s390x
cmd/internal/obj/x86
runtime/cgo
cmd/asm/internal/flags
cmd/asm/internal/lex
cmd/internal/bio
cmd/compile/internal/big
cmd/compile/internal/syntax
cmd/asm/internal/arch
cmd/compile/internal/ssa
cmd/asm/internal/asm
cmd/internal/gcprog
cmd/compile/internal/test
cmd/asm
cmd/internal/browser
cmd/cover
cmd/dist
cmd/doc
cmd/fix
cmd/go
cmd/gofmt
cmd/internal/pprof/profile
cmd/internal/pprof/plugin
cmd/internal/pprof/report
cmd/internal/pprof/svg
cmd/internal/pprof/tempfile
cmd/internal/pprof/commands
cmd/internal/pprof/driver
cmd/internal/pprof/fetch
cmd/internal/pprof/symbolizer
cmd/internal/pprof/symbolz
cmd/link/internal/ld
cmd/nm
cmd/objdump
cmd/pack
cmd/pprof
cmd/trace
cmd/link/internal/amd64
cmd/link/internal/arm
cmd/link/internal/arm64
cmd/link/internal/mips64
cmd/link/internal/ppc64
cmd/link/internal/s390x
cmd/link/internal/x86
cmd/vet/internal/cfg
cmd/link
cmd/vet/internal/whitelist
cmd/vet
cmd/compile/internal/gc
cmd/compile/internal/amd64
cmd/compile/internal/arm
cmd/compile/internal/arm64
cmd/compile/internal/mips64
cmd/compile/internal/ppc64
cmd/compile/internal/s390x
cmd/compile/internal/x86
cmd/compile


---
Installed Go for windows/amd64 in c:\Users\Alex\dev\go
Installed commands in c:\Users\Alex\dev\go\bin

c:\Users\Alex\dev\go\src>go run %GOPATH%\src\issue\go\34474\main.go
2019/09/26 14:31:44 Setting finalizer

c:\Users\Alex\dev\go\src>go run %GOPATH%\src\issue\go\34474\main.go
2019/09/26 14:31:48 Setting finalizer

c:\Users\Alex\dev\go\src>go run %GOPATH%\src\issue\go\34474\main.go
2019/09/26 14:31:50 Setting finalizer

c:\Users\Alex\dev\go\src>git checkout ca4089a
Previous HEAD position was faf611a07a... net/http: rename Post's parameter from bodyType to contentType
HEAD is now at ca4089ad62... cmd/compile: args no longer live until end-of-function

c:\Users\Alex\dev\go\src>git status
HEAD detached at ca4089ad62
nothing to commit, working tree clean

c:\Users\Alex\dev\go\src>make
##### Building Go bootstrap tool.
cmd/dist

##### Building Go toolchain using c:\users\alex\dev\go1.4.3.
bootstrap/internal/dwarf
bootstrap/internal/sys
bootstrap/asm/internal/flags
bootstrap/internal/bio
bootstrap/compile/internal/big
bootstrap/internal/obj
bootstrap/compile/internal/syntax
bootstrap/internal/gcprog
bootstrap/internal/obj/arm
bootstrap/internal/obj/arm64
bootstrap/internal/obj/mips
bootstrap/internal/obj/ppc64
bootstrap/internal/obj/s390x
bootstrap/internal/obj/x86
bootstrap/asm/internal/lex
bootstrap/link/internal/ld
bootstrap/asm/internal/arch
bootstrap/compile/internal/ssa
bootstrap/asm/internal/asm
bootstrap/asm
bootstrap/link/internal/amd64
bootstrap/link/internal/arm
bootstrap/link/internal/arm64
bootstrap/link/internal/mips64
bootstrap/link/internal/ppc64
bootstrap/link/internal/s390x
bootstrap/link/internal/x86
bootstrap/link
bootstrap/compile/internal/gc
bootstrap/compile/internal/amd64
bootstrap/compile/internal/arm
bootstrap/compile/internal/arm64
bootstrap/compile/internal/mips64
bootstrap/compile/internal/ppc64
bootstrap/compile/internal/s390x
bootstrap/compile/internal/x86
bootstrap/compile

##### Building go_bootstrap for host, windows/amd64.
runtime/internal/sys
runtime/internal/atomic
runtime
encoding
errors
internal/race
internal/syscall/windows/sysdll
math
sort
sync/atomic
unicode
unicode/utf16
unicode/utf8
container/heap
sync
internal/singleflight
io
syscall
bytes
hash
strings
hash/adler32
bufio
strconv
path
reflect
encoding/base64
crypto
regexp/syntax
crypto/sha1
internal/syscall/windows
internal/syscall/windows/registry
time
regexp
os
encoding/binary
fmt
os/signal
path/filepath
io/ioutil
context
flag
go/token
log
net/url
text/template/parse
compress/flate
encoding/json
debug/dwarf
os/exec
go/scanner
compress/zlib
text/template
go/ast
debug/macho
debug/elf
go/doc
go/parser
go/build
cmd/go
        1 file(s) moved.

##### Building packages and commands for windows/amd64.
runtime/internal/sys
runtime/internal/atomic
runtime
errors
internal/race
sync/atomic
unicode
unicode/utf8
math
sync
internal/syscall/windows/sysdll
io
unicode/utf16
syscall
sort
bytes
strings
strconv
bufio
path
reflect
hash
regexp/syntax
hash/adler32
text/tabwriter
hash/crc32
internal/syscall/windows
internal/syscall/windows/registry
compress/bzip2
time
regexp
container/heap
container/list
container/ring
crypto
crypto/subtle
crypto/cipher
math/rand
os
crypto/sha512
crypto/aes
encoding/binary
crypto/hmac
crypto/md5
crypto/rc4
crypto/sha1
crypto/sha256
cmd/internal/sys
fmt
path/filepath
crypto/des
encoding/base64
internal/nettrace
internal/singleflight
io/ioutil
encoding/pem
encoding
encoding/ascii85
encoding/base32
hash/crc64
hash/fnv
cmd/internal/dwarf
flag
log
debug/dwarf
compress/flate
debug/gosym
cmd/internal/obj
debug/plan9obj
cmd/vendor/golang.org/x/arch/arm/armasm
compress/zlib
debug/macho
debug/elf
cmd/internal/goobj
debug/pe
cmd/vendor/golang.org/x/arch/x86/x86asm
archive/tar
archive/zip
compress/gzip
compress/lzw
context
math/big
encoding/hex
net
database/sql/driver
database/sql
cmd/internal/objfile
cmd/addr2line
encoding/csv
encoding/gob
crypto/dsa
crypto/elliptic
encoding/asn1
crypto/rand
crypto/rsa
crypto/ecdsa
crypto/x509/pkix
encoding/json
encoding/xml
vendor/golang_org/x/net/http2/hpack
crypto/x509
vendor/golang_org/x/net/idna
vendor/golang_org/x/net/lex/httplex
mime
mime/quotedprintable
crypto/tls
net/textproto
net/http/httptrace
net/http/internal
net/url
go/token
mime/multipart
go/scanner
text/template/parse
go/constant
go/ast
os/exec
text/template
net/http
text/scanner
go/parser
go/printer
go/doc
go/types
go/format
html
go/build
html/template
image/color
index/suffixarray
image
image/color/palette
runtime/debug
runtime/pprof
image/internal/imageutil
image/draw
image/jpeg
image/gif
image/png
runtime/trace
go/internal/gccgoimporter
go/internal/gcimporter
testing
expvar
internal/trace
go/importer
internal/testenv
log/syslog
math/cmplx
net/http/cgi
net/http/cookiejar
net/http/httptest
net/http/httputil
net/http/pprof
net/internal/socktest
net/http/fcgi
net/mail
net/rpc
net/smtp
os/signal
os/user
plugin
cmd/cgo
runtime/race
testing/iotest
testing/quick
net/rpc/jsonrpc
cmd/api
cmd/internal/obj/arm
cmd/internal/obj/arm64
cmd/internal/obj/mips
cmd/internal/obj/ppc64
cmd/internal/obj/s390x
cmd/internal/obj/x86
runtime/cgo
cmd/asm/internal/flags
cmd/asm/internal/lex
cmd/internal/bio
cmd/compile/internal/big
cmd/compile/internal/syntax
cmd/asm/internal/arch
cmd/asm/internal/asm
cmd/asm
cmd/compile/internal/ssa
cmd/internal/gcprog
cmd/compile/internal/test
cmd/internal/browser
cmd/cover
cmd/dist
cmd/doc
cmd/fix
cmd/go
cmd/gofmt
cmd/internal/pprof/profile
cmd/internal/pprof/plugin
cmd/internal/pprof/report
cmd/internal/pprof/svg
cmd/internal/pprof/tempfile
cmd/internal/pprof/commands
cmd/internal/pprof/driver
cmd/internal/pprof/fetch
cmd/internal/pprof/symbolizer
cmd/internal/pprof/symbolz
cmd/link/internal/ld
cmd/nm
cmd/objdump
cmd/pack
cmd/pprof
cmd/link/internal/amd64
cmd/link/internal/arm
cmd/link/internal/arm64
cmd/link/internal/mips64
cmd/link/internal/ppc64
cmd/link/internal/s390x
cmd/link/internal/x86
cmd/trace
cmd/link
cmd/vet/internal/cfg
cmd/vet/internal/whitelist
cmd/vet
cmd/compile/internal/gc
cmd/compile/internal/amd64
cmd/compile/internal/arm
cmd/compile/internal/arm64
cmd/compile/internal/mips64
cmd/compile/internal/ppc64
cmd/compile/internal/s390x
cmd/compile/internal/x86
cmd/compile


---
Installed Go for windows/amd64 in c:\Users\Alex\dev\go
Installed commands in c:\Users\Alex\dev\go\bin

c:\Users\Alex\dev\go\src>go run %GOPATH%\src\issue\go\34474\main.go
2019/09/26 14:33:55 Setting finalizer
panic: finalized message before returning from window proc!

goroutine 1 [running, locked to thread]:
panic(0x4a48c0, 0xc042004110)
        c:/users/alex/dev/go/src/runtime/panic.go:496 +0x1ae
main.windowProc(0x9069a, 0x31f, 0x1, 0x0, 0x8fc30)
        c:/users/alex/dev/src/issue/go/34474/main.go:157 +0x1aa
syscall.Syscall(0x7ffcf11fc3c0, 0x1, 0xc0420461b0, 0x0, 0x0, 0x0, 0x428323, 0xc042051d58)
        c:/users/alex/dev/go/src/runtime/syscall_windows.go:163 +0x69
syscall.(*Proc).Call(0xc0420067e0, 0xc042004118, 0x1, 0x1, 0x8, 0x4a5c00, 0x49fc01, 0xc042004118)
        c:/users/alex/dev/go/src/syscall/dll_windows.go:147 +0xae
syscall.(*LazyProc).Call(0xc0420460c0, 0xc042004118, 0x1, 0x1, 0xc0420460f0, 0xc042006380, 0x4, 0x4)
        c:/users/alex/dev/go/src/syscall/dll_windows.go:303 +0x66
main.dispatchMessage(0xc0420461b0)
        c:/users/alex/dev/src/issue/go/34474/main.go:207 +0x113
main.CreateWindow(0x4a3d80, 0x1)
        c:/users/alex/dev/src/issue/go/34474/main.go:87 +0x128
main.main()
        c:/users/alex/dev/src/issue/go/34474/main.go:72 +0x29
exit status 2

c:\Users\Alex\dev\go\src>

Alex

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Sep 27, 2019

Sorry, I don't doubt that it failed there, it's just that I would think that it was fixed, and then failed again more recently. Has this really been broken for over three years and nobody noticed?

@mdempsky
Copy link
Member

@mdempsky mdempsky commented Sep 27, 2019

Has this really been broken for over three years and nobody noticed?

Maybe our Windows users are just still using Go 1.7. ;[

@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Sep 28, 2019

I would think that it was fixed, and then failed again more recently.

I used git bisect command between bbe5da4 and current tip - that is how I discovered broken ca4089a commit. I suspect all commits after ca4089a are broken. I do not know how to prove that. If you have suggestion, I am all years. Also you need to be aware that test above sometimes succeeds even with broken commit - it is not 100% test. So sometimes I have to run test multiple times to make sure.

Has this really been broken for over three years and nobody noticed?

I would not be surprised, if it was broken for that long. The test above uses callbacks. And callbacks are manly used in Windows GUI. I don't think there are many Go Windows GUI programs / packages. I believe @zx2c4 built / supports Windows Go GUI program. Maybe he had some unexplained crashes.

And I am sure, we have some test around this area, but obviously they are not enough. And as Go runtime changed over time, maybe tests became useless.

Alex

@eliasnaur
Copy link
Contributor Author

@eliasnaur eliasnaur commented Sep 28, 2019

I would think that it was fixed, and then failed again more recently.

I used git bisect command between bbe5da4 and current tip - that is how I discovered broken ca4089a commit. I suspect all commits after ca4089a are broken. I do not know how to prove that. If you have suggestion, I am all years. Also you need to be aware that test above sometimes succeeds even with broken commit - it is not 100% test. So sometimes I have to run test multiple times to make sure.

Has this really been broken for over three years and nobody noticed?

I would not be surprised, if it was broken for that long. The test above uses callbacks. And callbacks are manly used in Windows GUI. I don't think there are many Go Windows GUI programs / packages. I believe @zx2c4 built / supports Windows Go GUI program. Maybe he had some unexplained crashes.

Note that I saw premature freeing even for calls without callbacks. For example, the CreateWindowEx call results in a garbled window title if wname is not runtime.KeepAlive'd. However, reproducing that behaviour requires visual inspection and a larger program. Using callbacks had the nice property that I could use a finalizer to prove my case much better than random crashes.

Related, my edit above said:

EDIT: Unrelated to DispatchMessage, I don't see anything about wname from line 177 moving to the heap, but I think it should.

@zx2c4
Copy link
Contributor

@zx2c4 zx2c4 commented Sep 28, 2019

I believe @zx2c4 built / supports Windows Go GUI program. Maybe he had some unexplained crashes.

Actually, yes. I wound up allocating certain objects explicitly on the Win32 heap in a fix to the lxn/walk package. I wouldn't be surprised if Alex's three year old commit was the culprit. When I showed up relatively recently, @lxn gave me the impression that the bug I was fixing (i.e. hacking around) had plagued him and his programs for quite a long time.

@zx2c4
Copy link
Contributor

@zx2c4 zx2c4 commented Sep 28, 2019

Here's the commit in question: lxn/walk@c0622b1

The analysis in that commit message is likely wrong, but it does directly correspond with the page permissions I was seeing in WinDbg at the time of the crash.

@zx2c4
Copy link
Contributor

@zx2c4 zx2c4 commented Sep 28, 2019

Here's a more minimal example:

package main

import (
	"runtime"
	"syscall"
	"unsafe"
)

func main() {
	finalizerCalled := false

	v := &struct{ b [9]byte }{}
	runtime.SetFinalizer(v, func(v *struct{ b [9]byte }) {
		finalizerCalled = true
	})

	(*syscall.Proc)(unsafe.Pointer(&struct {
		dll  uintptr
		name string
		addr uintptr
	}{addr: syscall.NewCallback(func(v uintptr) uintptr {
		runtime.GC()
		if finalizerCalled {
			println("UaF")
		}
		// Assume that rather than a Go-managed callback, this is
		// actually some system function that dereferences v.
		return 0
	})})).Call(uintptr(unsafe.Pointer(v)))

	// Uncomment this next line to remove the UaF.
	//runtime.KeepAlive(v)
}
@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Sep 28, 2019

Note that I saw premature freeing even for calls without callbacks. For example, the CreateWindowEx call results in a garbled window title if wname is not runtime.KeepAlive'd.

I am pretty sure (I did not check), that windowProc is called by the time CreateWindowEx returns.

I wouldn't be surprised if Alex's three year old commit was the culprit.

Which commit are you referring to? Which Alex are you referring to?

When I showed up relatively recently, @lxn gave me the impression that the bug I was fixing (i.e. hacking around) had plagued him and his programs for quite a long time.

@lxn you should always come and complain here at the source. We don't always help, but we always try. And you will get more eyes here.

Here's a more minimal example:

Sure. I can reproduce this on current tip too. Nice test we could add to the repo. Thank you.

Alex

@zx2c4
Copy link
Contributor

@zx2c4 zx2c4 commented Oct 1, 2019

Hey @randall77 - could you take a look at the PoC in #34474 (comment) failing due to your commit ca4089a?

I've started to hack up the compiler to fix this and I'm sure I'll get it working eventually but it occurred to me that somebody who actually works on the Go compiler might be better suited to formulate a proper fix.

@randall77
Copy link
Contributor

@randall77 randall77 commented Oct 1, 2019

I'm not sure what the fix would be. Somehow find OpConvert ops that convert unsafe.Pointer to uintptr and are then used in a call. Extend the lifetime of the arg of OpConvert to include the callsite.

There is related code in cmd/compile/internal/gc/plive.go, (*Liveness).markUnsafePoints.

But maybe there's a better way to make this happen. Maybe introduce OpKeepAlive(x) ops after calls for every argument which is OpConvert(x) where x is an unsafe.Pointer type.

@mdempsky
Copy link
Member

@mdempsky mdempsky commented Oct 1, 2019

I thought order.go already tries to insert KeepAlive for temps introduced for calls to //go:uintprescapes involving unsafe.Pointer=>uintptr conversions?

@randall77
Copy link
Contributor

@randall77 randall77 commented Oct 1, 2019

Indeed, that code looks like it already exists. Why is it not working?

@mdempsky
Copy link
Member

@mdempsky mdempsky commented Oct 1, 2019

Not sure. I can take a look later this week if no one beats me to it.

@mdempsky
Copy link
Member

@mdempsky mdempsky commented Oct 1, 2019

Had a little bit of time to look into it. It's just because Order.call only checks OCALLFUNC, and LazyProc.Call uses OCALLMETH.

@gopherbot
Copy link

@gopherbot gopherbot commented Oct 1, 2019

Change https://golang.org/cl/198043 mentions this issue: cmd/compile: fix //go:uintptrescapes for methods

@mdempsky
Copy link
Member

@mdempsky mdempsky commented Oct 1, 2019

If Windows folks could test CL 198043, that would be great. I think it should address the issues with LazyProc reported here, but I don't have a Windows computer to test with.

There are some more edge cases I want to consider before submitting it, but I don't think those are likely to affect real programs.

I'm also curious whether this merits backport to Go 1.13? It's a regression from an older Go release, but much older, so maybe not?

@eliasnaur
Copy link
Contributor Author

@eliasnaur eliasnaur commented Oct 1, 2019

Thanks. If the fix is simple and obvious (in hindsight), I had hoped it would qualify for a 1.13 backport. The crashes and workarounds are subtle, and my work (Gio on Window) requires Go 1.13 anyway.

@eliasnaur
Copy link
Contributor Author

@eliasnaur eliasnaur commented Oct 1, 2019

@mdempsky with your CL 198043 applied, my program no longer crashes and @zx2c4's program no longer prints "UaF".

@mdempsky
Copy link
Member

@mdempsky mdempsky commented Oct 1, 2019

@eliasnaur Great, thanks for the confirmation!

@zx2c4
Copy link
Contributor

@zx2c4 zx2c4 commented Oct 1, 2019

@gopherbot please backport this, because mis-compilations are dangerous and the fix appears to be a trivial single line fix.

@gopherbot
Copy link

@gopherbot gopherbot commented Oct 1, 2019

Backport issue(s) opened: #34641 (for 1.12), #34642 (for 1.13).

Remember to create the cherry-pick CL(s) as soon as the patch is submitted to master, according to https://golang.org/wiki/MinorReleases.

@eliasnaur
Copy link
Contributor Author

@eliasnaur eliasnaur commented Oct 10, 2019

I'm marking this for Go 1.14 because I think some fix should make the release. If nothing else, I'd like the CL 198043 fix for regular LazyProc.Calls to go in.

@gopherbot gopherbot closed this in b3bd7ab Nov 5, 2019
@golang golang locked and limited conversation to collaborators Nov 4, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
9 participants
You can’t perform that action at this time.