Files
sys/windows/svc/security.go
Alex Brainman 280f808b4a windows/svc: add IsWindowsService function
CL 244958 includes isWindowsService function that determines if a
process is running as a service. The code of the function is based on
public .Net implementation.

IsAnInteractiveSession function implements similar functionality, but
is based on an old Stackoverflow post., which is not as authoritative
as code written by Microsoft for their official product.

This change copies CL 244958 isWindowsService function into svc package
and makes it public. The intention is that future users will prefer 
IsWindowsService to IsAnInteractiveSession.

Also this change adds "Deprecated" comment to IsAnInteractiveSession to
point future users to IsWindowsService.

Call to IsAnInteractiveSession is also replaced with IsWindowsService
in golang.org/x/sys/windows/svc/example package.

Change-Id: I4a33b7f590ee8161d1134d8e83668e9da4e6b434
Reviewed-on: https://go-review.googlesource.com/c/sys/+/259397
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Trust: Brad Fitzpatrick <bradfitz@golang.org>
Trust: Alex Brainman <alex.brainman@gmail.com>
2020-10-08 06:31:27 +00:00

158 lines
4.4 KiB
Go

// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package svc
import (
"errors"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
func allocSid(subAuth0 uint32) (*windows.SID, error) {
var sid *windows.SID
err := windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY,
1, subAuth0, 0, 0, 0, 0, 0, 0, 0, &sid)
if err != nil {
return nil, err
}
return sid, nil
}
// IsAnInteractiveSession determines if calling process is running interactively.
// It queries the process token for membership in the Interactive group.
// http://stackoverflow.com/questions/2668851/how-do-i-detect-that-my-application-is-running-as-service-or-in-an-interactive-s
//
// Deprecated: Use IsWindowsService instead.
func IsAnInteractiveSession() (bool, error) {
interSid, err := allocSid(windows.SECURITY_INTERACTIVE_RID)
if err != nil {
return false, err
}
defer windows.FreeSid(interSid)
serviceSid, err := allocSid(windows.SECURITY_SERVICE_RID)
if err != nil {
return false, err
}
defer windows.FreeSid(serviceSid)
t, err := windows.OpenCurrentProcessToken()
if err != nil {
return false, err
}
defer t.Close()
gs, err := t.GetTokenGroups()
if err != nil {
return false, err
}
for _, g := range gs.AllGroups() {
if windows.EqualSid(g.Sid, interSid) {
return true, nil
}
if windows.EqualSid(g.Sid, serviceSid) {
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
// 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)
if err != nil {
return false, err
}
if psid != 0 {
// parent session id should be 0 for service process
return false, nil
}
pproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pbi[5]))
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
}
}
const (
servicesLower = "services.exe"
servicesUpper = "SERVICES.EXE"
)
i := int(exeNameLen) - 1
j := len(servicesLower) - 1
if i < j {
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--
}
}
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:])
}