tailscale/tailscale · error
EnableCurrentThreadPrivileges(%#v): %w
Error message
EnableCurrentThreadPrivileges(%#v): %w
What it means
Before CreateProcessAsUser, EnableCurrentThreadPrivileges enables SeAssignPrimaryTokenPrivilege and SeIncreaseQuotaPrivilege on the calling thread; both are required to assign the session token as the child's primary token. It fails - classically with ERROR_NOT_ALL_ASSIGNED - when the calling process's token does not hold those privileges, i.e. the caller is not SYSTEM.
Source
Thrown at util/winutil/restartmgr_windows.go:810
if err != nil {
return nil, fmt.Errorf("UTF16PtrFromString(wd): %w", err)
}
env, err := token.Environ(false)
if err != nil {
return nil, fmt.Errorf("token environment: %w", err)
}
env16 := NewEnvBlock(env)
// The privileges in privNames are required for CreateProcessAsUser to be
// able to start processes as other users in other logon sessions.
privNames := []string{
"SeAssignPrimaryTokenPrivilege",
"SeIncreaseQuotaPrivilege",
}
dropPrivs, err := EnableCurrentThreadPrivileges(privNames)
if err != nil {
return nil, fmt.Errorf("EnableCurrentThreadPrivileges(%#v): %w", privNames, err)
}
defer dropPrivs()
createFlags := extraFlags | windows.CREATE_UNICODE_ENVIRONMENT | windows.DETACHED_PROCESS
si := windows.StartupInfo{
Cb: uint32(unsafe.Sizeof(windows.StartupInfo{})),
Desktop: defaultDesktop,
}
var pi windows.ProcessInformation
if err := windows.CreateProcessAsUser(token, exePath16, commandLine16, nil, nil,
false, createFlags, env16, wd16, &si, &pi); err != nil {
return nil, fmt.Errorf("CreateProcessAsUser: %w", err)
}
return &pi, nil
}
// NewEnvBlock processes a slice of strings containing "NAME=value" pairs
// representing a process envionment into the environment block format used byView on GitHub (pinned to 6e0912f979)
Solutions
- Run the launching process as LocalSystem (WTSQueryUserToken in this same path requires it anyway)
- Verify with 'whoami /priv' that both privileges are listed before invoking the API
- If a custom service account is mandatory, grant it the two privileges via policy
Defensive patterns
Strategy: validation
Validate before calling
// verify the launching process holds the privileges before starting
func canLaunchAsUser() bool {
var tok windows.Token
if err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_QUERY, &tok); err != nil {
return false
}
defer tok.Close()
// SYSTEM holds SeAssignPrimaryTokenPrivilege/SeIncreaseQuotaPrivilege by default;
// user tokens do not. Check elevation/identity as the practical gate.
return tok.IsElevated()
} Try / catch
pi, err := startProcessInSession(sessID, cli)
if err != nil {
if errors.Is(err, windows.ERROR_NOT_ALL_ASSIGNED) || strings.Contains(err.Error(), "EnableCurrentThreadPrivileges") {
return errors.New("launcher must run as LocalSystem with SeAssignPrimaryTokenPrivilege")
}
return err
} Prevention
- Run the launcher as LocalSystem; verify with 'whoami /priv' on the service context
- Ensure group policy has not stripped SeAssignPrimaryTokenPrivilege or SeIncreaseQuotaPrivilege
- Fail fast at service startup if privileges are missing
When it happens
Trigger: startProcessInSession* invoked by a process running as a regular user or a restricted service account; group policy stripping the privileges; the privilege names failing LsaLookupPrivilegeValue.
Common situations: Running the launcher outside the Windows service for testing; service configured as NETWORK SERVICE instead of LocalSystem; hardened machines where those privileges were removed.
Related errors
- SetICMPErrImmunity: getting SyscallConn failed: %v
- SetICMPErrImmunity: could not set SIO_UDP_NETRESET: %v
- SetICMPErrImmunity: SyscallConn.Control failed: %v
- failed to open the %s key: %w
- failed to get token user: %w
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/45744609b2c62770.
Report an issue: GitHub.