windows/svc: rewrite IsWindowsService
Alex copied this from a temporary thing I had written for the runtime
package. In runtime, you can't really access other packages like
syscall, so everything has to be very manual. But in x/sys, we can do
things properly. So this reimplements the function in a more straight
forward way.
Change-Id: I1634904bb1e10f33252954ce02d4b17ae56592e5
Reviewed-on: https://go-review.googlesource.com/c/sys/+/298830
Trust: Jason A. Donenfeld <Jason@zx2c4.com>
Run-TryBot: Jason A. Donenfeld <Jason@zx2c4.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/windows/svc/security.go b/windows/svc/security.go
index 8cc6778..ef719c1 100644
--- a/windows/svc/security.go
+++ b/windows/svc/security.go
@@ -7,8 +7,8 @@
package svc
import (
- "errors"
- "syscall"
+ "path/filepath"
+ "strings"
"unsafe"
"golang.org/x/sys/windows"
@@ -64,101 +64,45 @@
return false, nil
}
-var (
- ntdll = windows.NewLazySystemDLL("ntdll.dll")
- _NtQueryInformationProcess = ntdll.NewProc("NtQueryInformationProcess")
-
- kernel32 = windows.NewLazySystemDLL("kernel32.dll")
- _QueryFullProcessImageNameA = kernel32.NewProc("QueryFullProcessImageNameA")
-)
-
// IsWindowsService reports whether the process is currently executing
// as a Windows service.
func IsWindowsService() (bool, error) {
- // This code was copied from runtime.isWindowsService function.
-
// 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".
- const _CURRENT_PROCESS = ^uintptr(0)
- // 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
- r0, _, _ := syscall.Syscall6(_NtQueryInformationProcess.Addr(), 5, _CURRENT_PROCESS, 0, uintptr(unsafe.Pointer(&pbi[0])), uintptr(unsafe.Sizeof(pbi)), uintptr(unsafe.Pointer(&pbiLen)), 0)
- if r0 != 0 {
- return false, errors.New("NtQueryInformationProcess failed: error=" + itoa(int(r0)))
- }
- var psid uint32
- err := windows.ProcessIdToSessionId(uint32(pbi[5]), &psid)
+
+ var pbi windows.PROCESS_BASIC_INFORMATION
+ pbiLen := uint32(unsafe.Sizeof(pbi))
+ err := windows.NtQueryInformationProcess(windows.CurrentProcess(), windows.ProcessBasicInformation, unsafe.Pointer(&pbi), pbiLen, &pbiLen)
if err != nil {
- if err == windows.ERROR_INVALID_PARAMETER {
- // This error happens when Windows cannot find process parent.
- // Perhaps process parent exited.
- // Assume we are not running in a service, because service
- // parent process (services.exe) cannot exit.
- return false, nil
- }
return false, err
}
- if psid != 0 {
- // parent session id should be 0 for service process
+ var psid uint32
+ err = windows.ProcessIdToSessionId(uint32(pbi.InheritedFromUniqueProcessId), &psid)
+ if err != nil || psid != 0 {
return false, nil
}
-
- pproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pbi[5]))
+ pproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pbi.InheritedFromUniqueProcessId))
if err != nil {
return false, err
}
defer windows.CloseHandle(pproc)
-
- // exeName gets the path to the executable image of the parent process
- var exeName [261]byte
- exeNameLen := uint32(len(exeName) - 1)
- r0, _, e0 := syscall.Syscall6(_QueryFullProcessImageNameA.Addr(), 4, uintptr(pproc), 0, uintptr(unsafe.Pointer(&exeName[0])), uintptr(unsafe.Pointer(&exeNameLen)), 0, 0)
- if r0 == 0 {
- if e0 != 0 {
- return false, e0
- } else {
- return false, syscall.EINVAL
- }
+ var exeNameBuf [261]uint16
+ exeNameLen := uint32(len(exeNameBuf) - 1)
+ err = windows.QueryFullProcessImageName(pproc, 0, &exeNameBuf[0], &exeNameLen)
+ if err != nil {
+ return false, err
}
- const (
- servicesLower = "services.exe"
- servicesUpper = "SERVICES.EXE"
- )
- i := int(exeNameLen) - 1
- j := len(servicesLower) - 1
- if i < j {
+ exeName := windows.UTF16ToString(exeNameBuf[:exeNameLen])
+ if !strings.EqualFold(filepath.Base(exeName), "services.exe") {
return false, nil
}
- for {
- if j == -1 {
- return i == -1 || exeName[i] == '\\', nil
- }
- if exeName[i] != servicesLower[j] && exeName[i] != servicesUpper[j] {
- return false, nil
- }
- i--
- j--
+ system32, err := windows.GetSystemDirectory()
+ if err != nil {
+ return false, err
}
-}
-
-func itoa(val int) string { // do it here rather than with fmt to avoid dependency
- if val < 0 {
- return "-" + itoa(-val)
- }
- var buf [32]byte // big enough for int64
- i := len(buf) - 1
- for val >= 10 {
- buf[i] = byte(val%10 + '0')
- i--
- val /= 10
- }
- buf[i] = byte(val + '0')
- return string(buf[i:])
+ targetExeName := filepath.Join(system32, "services.exe")
+ return strings.EqualFold(exeName, targetExeName), nil
}