cmd/tailscaled: change Windows implementation to shut down subprocess via closing its stdin

We've been doing a hard kill of the subprocess, which is only safe as long as
both the cli and gui are not running and the subprocess has had the opportunity
to clean up DNS settings etc. If unattended mode is turned on, this is definitely
unsafe.

I changed babysitProc to close the subprocess's stdin to make it shut down, and
then I plumbed a cancel function into the stdin reader on the subprocess side.

Fixes https://github.com/tailscale/corp/issues/5621

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
This commit is contained in:
Aaron Klotz 2022-12-09 10:14:39 -07:00
parent 9c67395334
commit 53e2010b8a

View File

@ -25,6 +25,7 @@
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/netip"
"os"
@ -273,17 +274,24 @@ func beWindowsSubprocess() bool {
log.Printf("Error reading environment config: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
go func() {
b := make([]byte, 16)
for {
_, err := os.Stdin.Read(b)
if err == io.EOF {
// Parent wants us to shut down gracefully.
log.Printf("subproc received EOF from stdin")
cancel()
return
}
if err != nil {
log.Fatalf("stdin err (parent process died): %v", err)
}
}
}()
err := startIPNServer(context.Background(), log.Printf, logid)
err := startIPNServer(ctx, log.Printf, logid)
if err != nil {
log.Fatalf("ipnserver: %v", err)
}
@ -367,6 +375,7 @@ func babysitProc(ctx context.Context, args []string, logf logger.Logf) {
var proc struct {
mu sync.Mutex
p *os.Process
wStdin *os.File
}
done := make(chan struct{})
@ -378,15 +387,18 @@ func babysitProc(ctx context.Context, args []string, logf logger.Logf) {
case sig = <-interrupt:
logf("babysitProc: got signal: %v", sig)
close(done)
case <-ctx.Done():
logf("babysitProc: context done")
sig = os.Kill
close(done)
}
proc.mu.Lock()
proc.p.Signal(sig)
proc.mu.Unlock()
case <-ctx.Done():
logf("babysitProc: context done")
close(done)
proc.mu.Lock()
// Closing wStdin gives the subprocess a chance to shut down cleanly,
// which is important for cleaning up DNS settings etc.
proc.wStdin.Close()
proc.mu.Unlock()
}
}()
bo := backoff.NewBackoff("babysitProc", logf, 30*time.Second)
@ -448,6 +460,7 @@ func babysitProc(ctx context.Context, args []string, logf logger.Logf) {
} else {
proc.mu.Lock()
proc.p = cmd.Process
proc.wStdin = wStdin
proc.mu.Unlock()
err = cmd.Wait()