]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.14] runtime: detect services in signal handler
authorJason A. Donenfeld <Jason@zx2c4.com>
Tue, 14 Jul 2020 07:41:03 +0000 (01:41 -0600)
committerDmitri Shuralyov <dmitshur@golang.org>
Sat, 22 Aug 2020 03:58:04 +0000 (03:58 +0000)
The service handler needs to handle CTRL+C-like events -- including
those sent by the service manager itself -- using the default Windows
implementation if no signal handler from Go is already listening to
those events. Ordinarily, the signal handler would call exit(2), but we
actually need to allow this to be passed onward to the service handler.
So, we detect if we're in a service and skip calling exit(2) in that
case, just like we do for shared libraries.

Updates #40167.
Updates #40074.
Fixes #40411.

Change-Id: Ia77871737a80e1e94f85b02d26af1fd2f646af96
Reviewed-on: https://go-review.googlesource.com/c/go/+/244958
Run-TryBot: Jason A. Donenfeld <Jason@zx2c4.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
src/runtime/os_windows.go

index 7f4ce14ef1647f6ab23ede069c1ef8156787aed2..4aaa31848af7fe15ee7f3a876f13b379dbc31e4a 100644 (file)
@@ -36,7 +36,10 @@ const (
 //go:cgo_import_dynamic runtime._SetThreadContext SetThreadContext%2 "kernel32.dll"
 //go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll"
 //go:cgo_import_dynamic runtime._LoadLibraryA LoadLibraryA%1 "kernel32.dll"
+//go:cgo_import_dynamic runtime._OpenProcess OpenProcess%3 "kernel32.dll"
 //go:cgo_import_dynamic runtime._PostQueuedCompletionStatus PostQueuedCompletionStatus%4 "kernel32.dll"
+//go:cgo_import_dynamic runtime._ProcessIdToSessionId ProcessIdToSessionId%2 "kernel32.dll"
+//go:cgo_import_dynamic runtime._QueryFullProcessImageNameA QueryFullProcessImageNameA%4 "kernel32.dll"
 //go:cgo_import_dynamic runtime._ResumeThread ResumeThread%1 "kernel32.dll"
 //go:cgo_import_dynamic runtime._SetConsoleCtrlHandler SetConsoleCtrlHandler%2 "kernel32.dll"
 //go:cgo_import_dynamic runtime._SetErrorMode SetErrorMode%1 "kernel32.dll"
@@ -84,7 +87,10 @@ var (
        _SetThreadContext,
        _LoadLibraryW,
        _LoadLibraryA,
+       _OpenProcess,
        _PostQueuedCompletionStatus,
+       _ProcessIdToSessionId,
+       _QueryFullProcessImageNameA,
        _QueryPerformanceCounter,
        _QueryPerformanceFrequency,
        _ResumeThread,
@@ -129,7 +135,8 @@ var (
        // Load ntdll.dll manually during startup, otherwise Mingw
        // links wrong printf function to cgo executable (see issue
        // 12030 for details).
-       _NtWaitForSingleObject stdFunction
+       _NtWaitForSingleObject     stdFunction
+       _NtQueryInformationProcess stdFunction
 
        // These are from non-kernel32.dll, so we prefer to LoadLibraryEx them.
        _timeBeginPeriod,
@@ -257,6 +264,7 @@ func loadOptionalSyscalls() {
                throw("ntdll.dll not found")
        }
        _NtWaitForSingleObject = windowsFindfunc(n32, []byte("NtWaitForSingleObject\000"))
+       _NtQueryInformationProcess = windowsFindfunc(n32, []byte("NtQueryInformationProcess\000"))
 
        if GOARCH == "arm" {
                _QueryPerformanceCounter = windowsFindfunc(k32, []byte("QueryPerformanceCounter\000"))
@@ -997,6 +1005,63 @@ func usleep(us uint32) {
        onosstack(usleep2Addr, 10*us)
 }
 
+// isWindowsService returns whether the process is currently executing as a
+// Windows service. The below technique looks a bit hairy, but it's actually
+// exactly what the .NET framework does for the similarly named function:
+// https://github.com/dotnet/extensions/blob/f4066026ca06984b07e90e61a6390ac38152ba93/src/Hosting/WindowsServices/src/WindowsServiceHelpers.cs#L26-L31
+// Specifically, it looks up whether the parent process has session ID zero
+// and is called "services".
+func isWindowsService() bool {
+       const (
+               _CURRENT_PROCESS                   = ^uintptr(0)
+               _PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
+       )
+       // pbi is a PROCESS_BASIC_INFORMATION struct, where we just care about
+       // the 6th pointer inside of it, which contains the pid of the process
+       // parent:
+       // https://github.com/wine-mirror/wine/blob/42cb7d2ad1caba08de235e6319b9967296b5d554/include/winternl.h#L1294
+       var pbi [6]uintptr
+       var pbiLen uint32
+       err := stdcall5(_NtQueryInformationProcess, _CURRENT_PROCESS, 0, uintptr(unsafe.Pointer(&pbi[0])), uintptr(unsafe.Sizeof(pbi)), uintptr(unsafe.Pointer(&pbiLen)))
+       if err != 0 {
+               return false
+       }
+       var psid uint32
+       err = stdcall2(_ProcessIdToSessionId, pbi[5], uintptr(unsafe.Pointer(&psid)))
+       if err == 0 || psid != 0 {
+               return false
+       }
+       pproc := stdcall3(_OpenProcess, _PROCESS_QUERY_LIMITED_INFORMATION, 0, pbi[5])
+       if pproc == 0 {
+               return false
+       }
+       defer stdcall1(_CloseHandle, pproc)
+       // exeName gets the path to the executable image of the parent process
+       var exeName [261]byte
+       exeNameLen := uint32(len(exeName) - 1)
+       err = stdcall4(_QueryFullProcessImageNameA, pproc, 0, uintptr(unsafe.Pointer(&exeName[0])), uintptr(unsafe.Pointer(&exeNameLen)))
+       if err == 0 || exeNameLen == 0 {
+               return false
+       }
+       servicesLower := "services.exe"
+       servicesUpper := "SERVICES.EXE"
+       i := int(exeNameLen) - 1
+       j := len(servicesLower) - 1
+       if i < j {
+               return false
+       }
+       for {
+               if j == -1 {
+                       return i == -1 || exeName[i] == '\\'
+               }
+               if exeName[i] != servicesLower[j] && exeName[i] != servicesUpper[j] {
+                       return false
+               }
+               i--
+               j--
+       }
+}
+
 func ctrlhandler1(_type uint32) uint32 {
        var s uint32
 
@@ -1012,7 +1077,11 @@ func ctrlhandler1(_type uint32) uint32 {
        if sigsend(s) {
                return 1
        }
-       exit(2) // SIGINT, SIGTERM, etc
+       if !islibrary && !isarchive && !isWindowsService() {
+               // Only exit the program if we don't have a DLL or service.
+               // See https://golang.org/issues/35965 and https://golang.org/issues/40167
+               exit(2) // SIGINT, SIGTERM, etc
+       }
        return 0
 }