{"record":{"id":"4a7b4efee648be35","repo":"tailscale/tailscale","slug":"lsalogonuser-q-w-substatus-v","errorCode":null,"errorMessage":"LsaLogonUser(%q): %w, SubStatus: %v","messagePattern":"LsaLogonUser\\(%q\\): %w, SubStatus: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/winutil/s4u/lsa_windows.go","lineNumber":344,"sourceCode":"\tvar srcContext _TOKEN_SOURCE\n\tcopy(srcContext.SourceName[:], []byte(srcName))\n\tif err := allocateLocallyUniqueId(&srcContext.SourceIdentifier); err != nil {\n\t\treturn 0, err\n\t}\n\n\toriginName, err := windows.NewNTString(srcName)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tvar profileBuf uintptr\n\tvar profileBufLen uint32\n\tvar logonID windows.LUID\n\tvar quotas _QUOTA_LIMITS\n\tvar subNTStatus windows.NTStatus\n\tntStatus := lsaLogonUser(ls.handle, originName, _Network, pkgID, authInfo, authInfoLen, nil, &srcContext, &profileBuf, &profileBufLen, &logonID, &token, &quotas, &subNTStatus)\n\tif e := wingoes.ErrorFromNTStatus(ntStatus); e.Failed() {\n\t\treturn 0, fmt.Errorf(\"LsaLogonUser(%q): %w, SubStatus: %v\", u.Username, e, subNTStatus)\n\t}\n\tif profileBuf != 0 {\n\t\tlsaFreeReturnBuffer(profileBuf)\n\t}\n\treturn token, nil\n}\n\n// samToUPN16 converts SAM-style account name samName to a UPN account name,\n// returned as a UTF-16 slice.\nfunc samToUPN16(samName string) (upn16 []uint16, err error) {\n\t_, samAccount, hasSep := strings.Cut(samName, `\\`)\n\tif !hasSep {\n\t\treturn nil, fmt.Errorf(\"%w: expected samName to contain a backslash\", os.ErrInvalid)\n\t}\n\n\t// This is essentially the same algorithm used by Win32-OpenSSH:\n\t// First, try obtaining a UPN directly...\n\tupn16, err = translateName(samName, windows.NameSamCompatible, windows.NameUserPrincipal)","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/util/winutil/s4u/lsa_windows.go#L326-L362","documentation":"The core LsaLogonUser S4U (service-for-user) call failed. The message carries both the primary NTSTATUS and SubStatus: the primary covers STATUS_LOGON_FAILURE / STATUS_ACCOUNT_RESTRICTION style failures, while SubStatus often pins the account-level reason (STATUS_ACCOUNT_DISABLED, STATUS_PASSWORD_EXPIRED, STATUS_INVALID_LOGON_HOURS, Kerberos KDC errors). LsaLogonUser itself requires the caller to hold SeTcbPrivilege.","triggerScenarios":"Target account disabled, locked out, or expired; logon-hours policy violated when capLevel == CapCreateProcess; Kerberos S4U refused because the KDC is unreachable or the computer account is broken; caller thread missing SeTcbPrivilege.","commonSituations":"SSH pre-auth group lookups (ListGroupIDsForSSHPreAuthOnly) and launching processes as arbitrary users; service-account policy changes by IT; DC outages; using a Session from a non-SYSTEM context.","solutions":["Read SubStatus in the message first: it distinguishes account state from infrastructure failure","Verify the account state (enabled, not locked, not expired) in AD or the SAM","Check KDC/DC reachability and time skew (Kerberos is time-sensitive)","Confirm the calling process runs as SYSTEM with SeTcbPrivilege"],"exampleFix":"// before\nsess, err := s4u.Login(logf, srcName, u, s4u.CapCreateProcess)\nif err != nil { return err } // opaque\n\n// after\nsess, err := s4u.Login(logf, srcName, u, s4u.CapCreateProcess)\nif err != nil {\n    if strings.Contains(err.Error(), \"STATUS_ACCOUNT_DISABLED\") {\n        return errors.New(\"account disabled; contact IT\")\n    }\n    if strings.Contains(err.Error(), \"STATUS_INVALID_LOGON_HOURS\") {\n        return errors.New(\"outside allowed logon hours\")\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"sess, err := s4u.Login(logf, srcName, u, capLevel)\nif err != nil {\n    msg := err.Error()\n    switch {\n    case strings.Contains(msg, \"STATUS_ACCOUNT_DISABLED\"):\n        return errors.New(\"account disabled\")\n    case strings.Contains(msg, \"STATUS_PASSWORD_EXPIRED\"):\n        return errors.New(\"account password expired\")\n    case strings.Contains(msg, \"STATUS_INVALID_LOGON_HOURS\"):\n        return errors.New(\"outside permitted logon hours\")\n    }\n    return err // includes primary NTSTATUS and SubStatus for diagnosis\n}","preventionTips":["Always read the SubStatus field in the message before guessing a cause","Run the calling service as SYSTEM so SeTcbPrivilege is present","Monitor DC health and clock skew on hosts performing S4U logons"],"tags":["go","windows","lsa","kerberos","s4u","logon","ntstatus"],"backgroundTag":"lsalogonuser-failed","analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}