go: syscall: Windows user32 function (SendInput) behaves incorrectly when called within golang environment
What version of Go are you using (go version
)?
go version go1.12.4 windows/amd64 OS Name: Microsoft Windows 10 Pro OS Version: 10.0.17763 N/A Build 17763
Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (go env
)?
go env
Output
set GOARCH=amd64 set GOBIN= set GOCACHE=C:\Users\knutz\AppData\Local\go-build set GOEXE=.exe set GOFLAGS= set GOHOSTARCH=amd64 set GOHOSTOS=windows set GOOS=windows set GOPATH=C:\Users\knutz\go set GOPROXY= set GORACE= set GOROOT=C:\Go set GOTMPDIR= set GOTOOLDIR=C:\Go\pkg\tool\windows_amd64 set GCCGO=gccgo 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 -fno-caret-diagnostics -Qunused-arguments -fmessag e-length=0 -fdebug-prefix-map=C:\Users\knutz\AppData\Local\Temp\go-build67967260 6=/tmp/go-build -gno-record-gcc-switches
What did you do?
In our application we use ffi to perform some things in native code on Windows. In particular, we use SendInput() on Windows which dispatches mouse events and keyboard events to the OS. When a DLL is loaded by the go runtime, this function does not behave as expected. This is also when using the syscall library directly with user32.dll to call the function using pure go code.
The unexpected behavior seems to be a result of how the runtime is setup in golang. This bug is quite serious because it might have unintended consequences when interacting with the windows runtime. It might be possible that other w32 functions behave differently than expected.
We are using the Windows user32 function SendInput: https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendinput
Go version
package main
import (
"log"
"syscall"
"time"
"unsafe"
)
var (
user32 = syscall.NewLazyDLL("user32.dll")
sendInputProc = user32.NewProc("SendInput")
)
func test() {
type keyboardInput struct {
wVk uint16
wScan uint16
dwFlags uint32
time uint32
dwExtraInfo uint64
}
type input struct {
inputType uint32
ki keyboardInput
padding uint64
}
var i input
i.inputType = 1 //INPUT_KEYBOARD
i.ki.wVk = 0x41 // virtual key code for a
ret, _, err := sendInputProc.Call(
uintptr(1),
uintptr(unsafe.Pointer(&i)),
uintptr(unsafe.Sizeof(i)),
)
log.Printf("ret: %v error: %v", ret, err)
}
func main() {
done := false
for !done {
test()
<-time.After(time.Second)
}
return
}
C version
#include <Windows.h>
#include <cstdio>
void test()
{
INPUT ip = {};
ip.type = INPUT_KEYBOARD;
ip.ki.wVk = 0x41; // virtual-key code for the "a" key
UINT ret = SendInput(1, &ip, sizeof(INPUT));
DWORD err = GetLastError();
std::printf("ret: %i err: %i\n", (int)ret, (int)err);
}
int main()
{
while(true)
{
test();
Sleep(1000);
}
}
What did you expect to see?
With both binaries:
- Run program, this will output a series of ‘a’ every second, as if typed
- Every time an ‘a’ is outputted, the program prints how many events were dispatched (the result from SendInput) as well as if there was an error
- Hit ctrl-alt-del to activate the secure desktop
- Wait for 2-3 seconds
- Go back, quit the program and check the output
Expected output (as in the C version): ret: 1 err: 0 ret: 1 err: 0 ret: 1 err: 0 ret: 0 err: 5 ret: 0 err: 5 ret: 0 err: 5 ret: 1 err: 5
ret: 1 means that 1 event was dispatched ret: 0 means that no events were dispatched err: 5 means access denied in windows
When the secure desktop was present, ret is 0 and err is 5. This is as expected. No events were dispatched and an error was generated.
What did you see instead?
In the golang version you get: 2019/04/25 14:20:43 ret: 1 error: The operation completed successfully. 2019/04/25 14:20:44 ret: 1 error: The operation completed successfully. 2019/04/25 14:20:45 ret: 1 error: The operation completed successfully. 2019/04/25 14:20:46 ret: 1 error: Access is denied. 2019/04/25 14:20:47 ret: 1 error: Access is denied. 2019/04/25 14:20:48 ret: 1 error: Access is denied. 2019/04/25 14:20:49 ret: 1 error: The operation completed successfully.
The bug ret is always 1 when you are at the secure desktop and SendInput is used in golang, regardless of whether or not an event was dispatched. We know that an event was not dispatched because an error is generated (5 = Access is denied.)
This is worrisome because you are not suppose to check for any errors unless the return value of SendInput is 0.
Remember that this has nothing to do with the syscall calling convention in golang or anything like that. You can put the c code snippet in a dll, call it from a standard C runtime and get a different result than if called from a go runtime.
This bug is quite serious for us because the behavior of user32 changes when interacting with go. It might be the case that other functions also behave erroneously.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 44 (23 by maintainers)
No, it removes undocumented behavior in exchange for the documented behavior.