From ba058bce86fcaa4b915dbae4fb8d0144d03a7eb2 Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Thu, 23 Jan 2025 15:15:00 -0600 Subject: [PATCH] cmd/tailscaled: defer COM initialization and enabling GP lock restrictions on Windows until main runs In this PR, we use a `tailscaledInit lazy.DeferredInit` to defer COM initialization and GP lock restriction until the main function is called and determines that the current process is an actual tailscaled process, rather than a CLI symlink, `tailscaled --version`, or another subcommand that doesn't require full initialization. Updates #cleanup Updates #14416 Signed-off-by: Nick Khyl --- cmd/tailscaled/tailscaled.go | 14 ++++++++++ cmd/tailscaled/tailscaled_windows.go | 41 +++++++++++++++------------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index bab3bc75a..2a1adefe4 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -59,6 +59,7 @@ import ( "tailscale.com/tsd" "tailscale.com/tsweb/varz" "tailscale.com/types/flagtype" + "tailscale.com/types/lazy" "tailscale.com/types/logger" "tailscale.com/types/logid" "tailscale.com/util/clientmetric" @@ -153,6 +154,14 @@ var subCommands = map[string]*func([]string) error{ var beCLI func() // non-nil if CLI is linked in +// tailscaledInit facilitates the lazy initialization of tailscaled. +// It defers potentially expensive and platform-specific +// initialization steps until the main function is called and +// determines that the current process is an actual tailscaled +// process, rather than a CLI symlink, `tailscaled --version`, or +// another subcommand that doesn't require full initialization. +var tailscaledInit lazy.DeferredInit + func main() { envknob.PanicIfAnyEnvCheckedInInit() envknob.ApplyDiskConfig() @@ -227,6 +236,11 @@ func main() { log.Fatalf("--bird-socket is not supported on %s", runtime.GOOS) } + if err := tailscaledInit.Do(); err != nil { + log.SetFlags(0) + log.Fatalf("tailscaled init failed: %v", err) + } + // Only apply a default statepath when neither have been provided, so that a // user may specify only --statedir if they wish. if args.statepath == "" && args.statedir == "" { diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index 7208e03da..d291415c8 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -60,31 +60,34 @@ import ( "tailscale.com/wf" ) -func init() { - // Initialize COM process-wide. - comProcessType := com.Service - if !isWindowsService() { - comProcessType = com.ConsoleApp - } - if err := com.StartRuntime(comProcessType); err != nil { - log.Printf("wingoes.com.StartRuntime(%d) failed: %v", comProcessType, err) - } -} - // permitPolicyLocks is a function to be called to lift the restriction on acquiring // [gp.PolicyLock]s once the service is running. // It is safe to be called multiple times. var permitPolicyLocks = func() {} func init() { - if isWindowsService() { - // We prevent [gp.PolicyLock]s from being acquired until the service enters the running state. - // Otherwise, if tailscaled starts due to a GPSI policy installing Tailscale, it may deadlock - // while waiting for the write counterpart of the GP lock to be released by Group Policy, - // which is itself waiting for the installation to complete and tailscaled to start. - // See tailscale/tailscale#14416 for more information. - permitPolicyLocks = gp.RestrictPolicyLocks() - } + tailscaledInit.Defer(func() error { + // Initialize COM process-wide. + comProcessType := com.Service + isService := isWindowsService() + if !isService { + comProcessType = com.ConsoleApp + } + if err := com.StartRuntime(comProcessType); err != nil { + return errors.New(fmt.Sprintf("wingoes.com.StartRuntime(%d) failed: %v", comProcessType, err)) + } + + if isService { + // We prevent [gp.PolicyLock]s from being acquired until the service enters the running state. + // Otherwise, if tailscaled starts due to a GPSI policy installing Tailscale, it may deadlock + // while waiting for the write counterpart of the GP lock to be released by Group Policy, + // which is itself waiting for the installation to complete and tailscaled to start. + // See tailscale/tailscale#14416 for more information. + permitPolicyLocks = gp.RestrictPolicyLocks() + } + + return nil + }) } const serviceName = "Tailscale"