mirror of
https://github.com/rwinkhart/sys.git
synced 2026-09-03 23:57:32 -04:00
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>
This commit is contained in:
+22
-78
@@ -7,8 +7,8 @@
|
|||||||
package svc
|
package svc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"path/filepath"
|
||||||
"syscall"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
@@ -64,101 +64,45 @@ func IsAnInteractiveSession() (bool, error) {
|
|||||||
return false, nil
|
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
|
// IsWindowsService reports whether the process is currently executing
|
||||||
// as a Windows service.
|
// as a Windows service.
|
||||||
func IsWindowsService() (bool, error) {
|
func IsWindowsService() (bool, error) {
|
||||||
// This code was copied from runtime.isWindowsService function.
|
|
||||||
|
|
||||||
// The below technique looks a bit hairy, but it's actually
|
// The below technique looks a bit hairy, but it's actually
|
||||||
// exactly what the .NET framework does for the similarly named function:
|
// 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
|
// 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
|
// Specifically, it looks up whether the parent process has session ID zero
|
||||||
// and is called "services".
|
// and is called "services".
|
||||||
const _CURRENT_PROCESS = ^uintptr(0)
|
|
||||||
// pbi is a PROCESS_BASIC_INFORMATION struct, where we just care about
|
var pbi windows.PROCESS_BASIC_INFORMATION
|
||||||
// the 6th pointer inside of it, which contains the pid of the process
|
pbiLen := uint32(unsafe.Sizeof(pbi))
|
||||||
// parent:
|
err := windows.NtQueryInformationProcess(windows.CurrentProcess(), windows.ProcessBasicInformation, unsafe.Pointer(&pbi), pbiLen, &pbiLen)
|
||||||
// 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)
|
|
||||||
if err != nil {
|
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
|
return false, err
|
||||||
}
|
}
|
||||||
if psid != 0 {
|
var psid uint32
|
||||||
// parent session id should be 0 for service process
|
err = windows.ProcessIdToSessionId(uint32(pbi.InheritedFromUniqueProcessId), &psid)
|
||||||
|
if err != nil || psid != 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
pproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pbi.InheritedFromUniqueProcessId))
|
||||||
pproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pbi[5]))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
defer windows.CloseHandle(pproc)
|
defer windows.CloseHandle(pproc)
|
||||||
|
var exeNameBuf [261]uint16
|
||||||
// exeName gets the path to the executable image of the parent process
|
exeNameLen := uint32(len(exeNameBuf) - 1)
|
||||||
var exeName [261]byte
|
err = windows.QueryFullProcessImageName(pproc, 0, &exeNameBuf[0], &exeNameLen)
|
||||||
exeNameLen := uint32(len(exeName) - 1)
|
if err != nil {
|
||||||
r0, _, e0 := syscall.Syscall6(_QueryFullProcessImageNameA.Addr(), 4, uintptr(pproc), 0, uintptr(unsafe.Pointer(&exeName[0])), uintptr(unsafe.Pointer(&exeNameLen)), 0, 0)
|
return false, err
|
||||||
if r0 == 0 {
|
|
||||||
if e0 != 0 {
|
|
||||||
return false, e0
|
|
||||||
} else {
|
|
||||||
return false, syscall.EINVAL
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const (
|
exeName := windows.UTF16ToString(exeNameBuf[:exeNameLen])
|
||||||
servicesLower = "services.exe"
|
if !strings.EqualFold(filepath.Base(exeName), "services.exe") {
|
||||||
servicesUpper = "SERVICES.EXE"
|
|
||||||
)
|
|
||||||
i := int(exeNameLen) - 1
|
|
||||||
j := len(servicesLower) - 1
|
|
||||||
if i < j {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
for {
|
system32, err := windows.GetSystemDirectory()
|
||||||
if j == -1 {
|
if err != nil {
|
||||||
return i == -1 || exeName[i] == '\\', nil
|
return false, err
|
||||||
}
|
|
||||||
if exeName[i] != servicesLower[j] && exeName[i] != servicesUpper[j] {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
i--
|
|
||||||
j--
|
|
||||||
}
|
}
|
||||||
}
|
targetExeName := filepath.Join(system32, "services.exe")
|
||||||
|
return strings.EqualFold(exeName, targetExeName), nil
|
||||||
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:])
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user