Skip to content

Commit 280f808

Browse files
committed
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 <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Trust: Brad Fitzpatrick <[email protected]> Trust: Alex Brainman <[email protected]>
1 parent a893ed3 commit 280f808

5 files changed

Lines changed: 135 additions & 3 deletions

File tree

windows/svc/example/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ func usage(errmsg string) {
3636
func main() {
3737
const svcName = "myservice"
3838

39-
isIntSess, err := svc.IsAnInteractiveSession()
39+
inService, err := svc.IsWindowsService()
4040
if err != nil {
41-
log.Fatalf("failed to determine if we are running in an interactive session: %v", err)
41+
log.Fatalf("failed to determine if we are running in service: %v", err)
4242
}
43-
if !isIntSess {
43+
if inService {
4444
runService(svcName, false)
4545
return
4646
}

windows/svc/security.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
package svc
88

99
import (
10+
"errors"
11+
"syscall"
12+
"unsafe"
13+
1014
"golang.org/x/sys/windows"
1115
)
1216

@@ -23,6 +27,8 @@ func allocSid(subAuth0 uint32) (*windows.SID, error) {
2327
// IsAnInteractiveSession determines if calling process is running interactively.
2428
// It queries the process token for membership in the Interactive group.
2529
// http://stackoverflow.com/questions/2668851/how-do-i-detect-that-my-application-is-running-as-service-or-in-an-interactive-s
30+
//
31+
// Deprecated: Use IsWindowsService instead.
2632
func IsAnInteractiveSession() (bool, error) {
2733
interSid, err := allocSid(windows.SECURITY_INTERACTIVE_RID)
2834
if err != nil {
@@ -57,3 +63,95 @@ func IsAnInteractiveSession() (bool, error) {
5763
}
5864
return false, nil
5965
}
66+
67+
var (
68+
ntdll = windows.NewLazySystemDLL("ntdll.dll")
69+
_NtQueryInformationProcess = ntdll.NewProc("NtQueryInformationProcess")
70+
71+
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
72+
_QueryFullProcessImageNameA = kernel32.NewProc("QueryFullProcessImageNameA")
73+
)
74+
75+
// IsWindowsService reports whether the process is currently executing
76+
// as a Windows service.
77+
func IsWindowsService() (bool, error) {
78+
// This code was copied from runtime.isWindowsService function.
79+
80+
// The below technique looks a bit hairy, but it's actually
81+
// exactly what the .NET framework does for the similarly named function:
82+
// https://github.com/dotnet/extensions/blob/f4066026ca06984b07e90e61a6390ac38152ba93/src/Hosting/WindowsServices/src/WindowsServiceHelpers.cs#L26-L31
83+
// Specifically, it looks up whether the parent process has session ID zero
84+
// and is called "services".
85+
const _CURRENT_PROCESS = ^uintptr(0)
86+
// pbi is a PROCESS_BASIC_INFORMATION struct, where we just care about
87+
// the 6th pointer inside of it, which contains the pid of the process
88+
// parent:
89+
// https://github.com/wine-mirror/wine/blob/42cb7d2ad1caba08de235e6319b9967296b5d554/include/winternl.h#L1294
90+
var pbi [6]uintptr
91+
var pbiLen uint32
92+
r0, _, _ := syscall.Syscall6(_NtQueryInformationProcess.Addr(), 5, _CURRENT_PROCESS, 0, uintptr(unsafe.Pointer(&pbi[0])), uintptr(unsafe.Sizeof(pbi)), uintptr(unsafe.Pointer(&pbiLen)), 0)
93+
if r0 != 0 {
94+
return false, errors.New("NtQueryInformationProcess failed: error=" + itoa(int(r0)))
95+
}
96+
var psid uint32
97+
err := windows.ProcessIdToSessionId(uint32(pbi[5]), &psid)
98+
if err != nil {
99+
return false, err
100+
}
101+
if psid != 0 {
102+
// parent session id should be 0 for service process
103+
return false, nil
104+
}
105+
106+
pproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pbi[5]))
107+
if err != nil {
108+
return false, err
109+
}
110+
defer windows.CloseHandle(pproc)
111+
112+
// exeName gets the path to the executable image of the parent process
113+
var exeName [261]byte
114+
exeNameLen := uint32(len(exeName) - 1)
115+
r0, _, e0 := syscall.Syscall6(_QueryFullProcessImageNameA.Addr(), 4, uintptr(pproc), 0, uintptr(unsafe.Pointer(&exeName[0])), uintptr(unsafe.Pointer(&exeNameLen)), 0, 0)
116+
if r0 == 0 {
117+
if e0 != 0 {
118+
return false, e0
119+
} else {
120+
return false, syscall.EINVAL
121+
}
122+
}
123+
const (
124+
servicesLower = "services.exe"
125+
servicesUpper = "SERVICES.EXE"
126+
)
127+
i := int(exeNameLen) - 1
128+
j := len(servicesLower) - 1
129+
if i < j {
130+
return false, nil
131+
}
132+
for {
133+
if j == -1 {
134+
return i == -1 || exeName[i] == '\\', nil
135+
}
136+
if exeName[i] != servicesLower[j] && exeName[i] != servicesUpper[j] {
137+
return false, nil
138+
}
139+
i--
140+
j--
141+
}
142+
}
143+
144+
func itoa(val int) string { // do it here rather than with fmt to avoid dependency
145+
if val < 0 {
146+
return "-" + itoa(-val)
147+
}
148+
var buf [32]byte // big enough for int64
149+
i := len(buf) - 1
150+
for val >= 10 {
151+
buf[i] = byte(val%10 + '0')
152+
i--
153+
val /= 10
154+
}
155+
buf[i] = byte(val + '0')
156+
return string(buf[i:])
157+
}

windows/svc/svc_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,23 @@ func TestExample(t *testing.T) {
132132
t.Errorf("%q string does not contain %q", string(out), want)
133133
}
134134
}
135+
136+
func TestIsAnInteractiveSession(t *testing.T) {
137+
isInteractive, err := svc.IsAnInteractiveSession()
138+
if err != nil {
139+
t.Fatal(err)
140+
}
141+
if !isInteractive {
142+
t.Error("IsAnInteractiveSession retuns false when running interactively.")
143+
}
144+
}
145+
146+
func TestIsWindowsService(t *testing.T) {
147+
isSvc, err := svc.IsWindowsService()
148+
if err != nil {
149+
t.Fatal(err)
150+
}
151+
if isSvc {
152+
t.Error("IsWindowsService retuns true when not running in a service.")
153+
}
154+
}

windows/syscall_windows.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
270270
//sys RegEnumKeyEx(key Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, classLen *uint32, lastWriteTime *Filetime) (regerrno error) = advapi32.RegEnumKeyExW
271271
//sys RegQueryValueEx(key Handle, name *uint16, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegQueryValueExW
272272
//sys GetCurrentProcessId() (pid uint32) = kernel32.GetCurrentProcessId
273+
//sys ProcessIdToSessionId(pid uint32, sessionid *uint32) (err error) = kernel32.ProcessIdToSessionId
273274
//sys GetConsoleMode(console Handle, mode *uint32) (err error) = kernel32.GetConsoleMode
274275
//sys SetConsoleMode(console Handle, mode uint32) (err error) = kernel32.SetConsoleMode
275276
//sys GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) = kernel32.GetConsoleScreenBufferInfo

windows/zsyscall_windows.go

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)