From b0d1d43c014ddb3da33b73f355a28f7b5bb0cb79 Mon Sep 17 00:00:00 2001 From: Alex Brainman Date: Sun, 21 Feb 2021 11:27:02 +1100 Subject: [PATCH] windows/svc: make IsWindowsService handle parent exit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IsWindowsService fails when parent process has exited. This change makes IsWindowsService return false instead. Added a test to verify the change. Change-Id: Ibb2aa9478e8afd9e35011bbdc0985bdf8f0af9cc Reviewed-on: https://go-review.googlesource.com/c/sys/+/294729 TryBot-Result: Go Bot Run-TryBot: Alex Brainman Reviewed-by: Jason A. Donenfeld Trust: Jason A. Donenfeld Trust: Alex Brainman --- windows/svc/security.go | 7 ++++ windows/svc/svc_test.go | 71 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/windows/svc/security.go b/windows/svc/security.go index da6df1d3..8cc67784 100644 --- a/windows/svc/security.go +++ b/windows/svc/security.go @@ -96,6 +96,13 @@ func IsWindowsService() (bool, error) { var psid uint32 err := windows.ProcessIdToSessionId(uint32(pbi[5]), &psid) 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 { diff --git a/windows/svc/svc_test.go b/windows/svc/svc_test.go index 5bf123d9..a6c8bbc9 100644 --- a/windows/svc/svc_test.go +++ b/windows/svc/svc_test.go @@ -171,3 +171,74 @@ func TestIsWindowsService(t *testing.T) { t.Error("IsWindowsService retuns true when not running in a service.") } } + +func TestIsWindowsServiceWhenParentExits(t *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") == "parent" { + // in parent process + + // Start the child and exit quickly. + child := exec.Command(os.Args[0], "-test.run=TestIsWindowsServiceWhenParentExits") + child.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=child") + err := child.Start() + if err != nil { + fmt.Fprintf(os.Stderr, fmt.Sprintf("child start failed: %v", err)) + os.Exit(1) + } + os.Exit(0) + } + + if os.Getenv("GO_WANT_HELPER_PROCESS") == "child" { + // in child process + dumpPath := os.Getenv("GO_WANT_HELPER_PROCESS_FILE") + if dumpPath == "" { + // We cannot report this error. But main test will notice + // that we did not create dump file. + os.Exit(1) + } + var msg string + isSvc, err := svc.IsWindowsService() + if err != nil { + msg = err.Error() + } + if isSvc { + msg = "IsWindowsService retuns true when not running in a service." + } + err = os.WriteFile(dumpPath, []byte(msg), 0644) + if err != nil { + // We cannot report this error. But main test will notice + // that we did not create dump file. + os.Exit(2) + } + os.Exit(0) + } + + // Run in a loop until it fails. + for i := 0; i < 10; i++ { + childDumpPath := filepath.Join(t.TempDir(), "issvc.txt") + + parent := exec.Command(os.Args[0], "-test.run=TestIsWindowsServiceWhenParentExits") + parent.Env = append(os.Environ(), + "GO_WANT_HELPER_PROCESS=parent", + "GO_WANT_HELPER_PROCESS_FILE="+childDumpPath) + parentOutput, err := parent.CombinedOutput() + if err != nil { + t.Errorf("parent failed: %v: %v", err, string(parentOutput)) + } + for i := 0; ; i++ { + if _, err := os.Stat(childDumpPath); err == nil { + break + } + time.Sleep(100 * time.Millisecond) + if i > 10 { + t.Fatal("timed out waiting for child ouput file to be created.") + } + } + childOutput, err := ioutil.ReadFile(childDumpPath) + if err != nil { + t.Fatalf("reading child ouput failed: %v", err) + } + if got, want := string(childOutput), ""; got != want { + t.Fatalf("child output: want %q, got %q", want, got) + } + } +}