--- /dev/null
+On Windows, [Current], [Lookup] and [LookupId] now supports the
+following built-in service user accounts:
+- `NT AUTHORITY\SYSTEM`
+- `NT AUTHORITY\LOCAL SERVICE`
+- `NT AUTHORITY\NETWORK SERVICE`
}
return (*TOKEN_GROUPS)(i), nil
}
+
+// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid_identifier_authority
+type SID_IDENTIFIER_AUTHORITY struct {
+ Value [6]byte
+}
+
+const (
+ SID_REVISION = 1
+ // https://learn.microsoft.com/en-us/windows/win32/services/localsystem-account
+ SECURITY_LOCAL_SYSTEM_RID = 18
+ // https://learn.microsoft.com/en-us/windows/win32/services/localservice-account
+ SECURITY_LOCAL_SERVICE_RID = 19
+ // https://learn.microsoft.com/en-us/windows/win32/services/networkservice-account
+ SECURITY_NETWORK_SERVICE_RID = 20
+)
+
+var SECURITY_NT_AUTHORITY = SID_IDENTIFIER_AUTHORITY{
+ Value: [6]byte{0, 0, 0, 0, 0, 5},
+}
+
+//sys IsValidSid(sid *syscall.SID) (valid bool) = advapi32.IsValidSid
+//sys GetSidIdentifierAuthority(sid *syscall.SID) (idauth *SID_IDENTIFIER_AUTHORITY) = advapi32.GetSidIdentifierAuthority
+//sys GetSidSubAuthority(sid *syscall.SID, subAuthorityIdx uint32) (subAuth *uint32) = advapi32.GetSidSubAuthority
+//sys GetSidSubAuthorityCount(sid *syscall.SID) (count *uint8) = advapi32.GetSidSubAuthorityCount
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx")
+ procGetSidIdentifierAuthority = modadvapi32.NewProc("GetSidIdentifierAuthority")
+ procGetSidSubAuthority = modadvapi32.NewProc("GetSidSubAuthority")
+ procGetSidSubAuthorityCount = modadvapi32.NewProc("GetSidSubAuthorityCount")
procImpersonateLoggedOnUser = modadvapi32.NewProc("ImpersonateLoggedOnUser")
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
+ procIsValidSid = modadvapi32.NewProc("IsValidSid")
procLogonUserW = modadvapi32.NewProc("LogonUserW")
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
procOpenSCManagerW = modadvapi32.NewProc("OpenSCManagerW")
return
}
+func GetSidIdentifierAuthority(sid *syscall.SID) (idauth *SID_IDENTIFIER_AUTHORITY) {
+ r0, _, _ := syscall.Syscall(procGetSidIdentifierAuthority.Addr(), 1, uintptr(unsafe.Pointer(sid)), 0, 0)
+ idauth = (*SID_IDENTIFIER_AUTHORITY)(unsafe.Pointer(r0))
+ return
+}
+
+func GetSidSubAuthority(sid *syscall.SID, subAuthorityIdx uint32) (subAuth *uint32) {
+ r0, _, _ := syscall.Syscall(procGetSidSubAuthority.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(subAuthorityIdx), 0)
+ subAuth = (*uint32)(unsafe.Pointer(r0))
+ return
+}
+
+func GetSidSubAuthorityCount(sid *syscall.SID) (count *uint8) {
+ r0, _, _ := syscall.Syscall(procGetSidSubAuthorityCount.Addr(), 1, uintptr(unsafe.Pointer(sid)), 0, 0)
+ count = (*uint8)(unsafe.Pointer(r0))
+ return
+}
+
func ImpersonateLoggedOnUser(token syscall.Token) (err error) {
r1, _, e1 := syscall.Syscall(procImpersonateLoggedOnUser.Addr(), 1, uintptr(token), 0, 0)
if r1 == 0 {
return
}
+func IsValidSid(sid *syscall.SID) (valid bool) {
+ r0, _, _ := syscall.Syscall(procIsValidSid.Addr(), 1, uintptr(unsafe.Pointer(sid)), 0, 0)
+ valid = r0 != 0
+ return
+}
+
func LogonUser(username *uint16, domain *uint16, password *uint16, logonType uint32, logonProvider uint32, token *syscall.Token) (err error) {
r1, _, e1 := syscall.Syscall6(procLogonUserW.Addr(), 6, uintptr(unsafe.Pointer(username)), uintptr(unsafe.Pointer(domain)), uintptr(unsafe.Pointer(password)), uintptr(logonType), uintptr(logonProvider), uintptr(unsafe.Pointer(token)))
if r1 == 0 {
}
}
+func isServiceAccount(sid *syscall.SID) bool {
+ if !windows.IsValidSid(sid) {
+ // We don't accept SIDs from the public API, so this should never happen.
+ // Better be on the safe side and validate anyway.
+ return false
+ }
+ // The following RIDs are considered service user accounts as per
+ // https://learn.microsoft.com/en-us/windows/win32/secauthz/well-known-sids and
+ // https://learn.microsoft.com/en-us/windows/win32/services/service-user-accounts:
+ // - "S-1-5-18": LocalSystem
+ // - "S-1-5-19": LocalService
+ // - "S-1-5-20": NetworkService
+ if *windows.GetSidSubAuthorityCount(sid) != windows.SID_REVISION ||
+ *windows.GetSidIdentifierAuthority(sid) != windows.SECURITY_NT_AUTHORITY {
+ return false
+ }
+ switch *windows.GetSidSubAuthority(sid, 0) {
+ case windows.SECURITY_LOCAL_SYSTEM_RID,
+ windows.SECURITY_LOCAL_SERVICE_RID,
+ windows.SECURITY_NETWORK_SERVICE_RID:
+ return true
+ }
+ return false
+}
+
+func isValidUserAccountType(sid *syscall.SID, sidType uint32) bool {
+ switch sidType {
+ case syscall.SidTypeUser:
+ return true
+ case syscall.SidTypeWellKnownGroup:
+ return isServiceAccount(sid)
+ }
+ return false
+}
+
+func isValidGroupAccountType(sidType uint32) bool {
+ switch sidType {
+ case syscall.SidTypeGroup:
+ return true
+ case syscall.SidTypeWellKnownGroup:
+ // Some well-known groups are also considered service accounts,
+ // so isValidUserAccountType would return true for them.
+ // We have historically allowed them in LookupGroup and LookupGroupId,
+ // so don't treat them as invalid here.
+ return true
+ case syscall.SidTypeAlias:
+ // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/7b2aeb27-92fc-41f6-8437-deb65d950921#gt_0387e636-5654-4910-9519-1f8326cf5ec0
+ // SidTypeAlias should also be treated as a group type next to SidTypeGroup
+ // and SidTypeWellKnownGroup:
+ // "alias object -> resource group: A group object..."
+ //
+ // Tests show that "Administrators" can be considered of type SidTypeAlias.
+ return true
+ }
+ return false
+}
+
// lookupUsernameAndDomain obtains the username and domain for usid.
-func lookupUsernameAndDomain(usid *syscall.SID) (username, domain string, e error) {
- username, domain, t, e := usid.LookupAccount("")
+func lookupUsernameAndDomain(usid *syscall.SID) (username, domain string, sidType uint32, e error) {
+ username, domain, sidType, e = usid.LookupAccount("")
if e != nil {
- return "", "", e
+ return "", "", 0, e
}
- if t != syscall.SidTypeUser {
- return "", "", fmt.Errorf("user: should be user account type, not %d", t)
+ if !isValidUserAccountType(usid, sidType) {
+ return "", "", 0, fmt.Errorf("user: should be user account type, not %d", sidType)
}
- return username, domain, nil
+ return username, domain, sidType, nil
}
// findHomeDirInRegistry finds the user home path based on the uid.
if e != nil {
return "", e
}
- // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/7b2aeb27-92fc-41f6-8437-deb65d950921#gt_0387e636-5654-4910-9519-1f8326cf5ec0
- // SidTypeAlias should also be treated as a group type next to SidTypeGroup
- // and SidTypeWellKnownGroup:
- // "alias object -> resource group: A group object..."
- //
- // Tests show that "Administrators" can be considered of type SidTypeAlias.
- if t != syscall.SidTypeGroup && t != syscall.SidTypeWellKnownGroup && t != syscall.SidTypeAlias {
+ if !isValidGroupAccountType(t) {
return "", fmt.Errorf("lookupGroupName: should be group account type, not %d", t)
}
return sid.String()
}
func newUserFromSid(usid *syscall.SID) (*User, error) {
- username, domain, e := lookupUsernameAndDomain(usid)
- if e != nil {
- return nil, e
- }
- gid, e := lookupUserPrimaryGroup(username, domain)
+ username, domain, sidType, e := lookupUsernameAndDomain(usid)
if e != nil {
return nil, e
}
if e != nil {
return nil, e
}
+ var gid string
+ if sidType == syscall.SidTypeWellKnownGroup {
+ // The SID does not contain a domain; this function's domain variable has
+ // been populated with the SID's identifier authority. This happens with
+ // special service user accounts such as "NT AUTHORITY\LocalSystem".
+ // In this case, gid is the same as the user SID.
+ gid = uid
+ } else {
+ gid, e = lookupUserPrimaryGroup(username, domain)
+ if e != nil {
+ return nil, e
+ }
+ }
// If this user has logged in at least once their home path should be stored
// in the registry under the specified SID. References:
// https://social.technet.microsoft.com/wiki/contents/articles/13895.how-to-remove-a-corrupted-user-profile-from-the-registry.aspx
if e != nil {
return nil, e
}
- if t != syscall.SidTypeUser {
+ if !isValidUserAccountType(sid, t) {
return nil, fmt.Errorf("user: should be user account type, not %d", t)
}
return newUserFromSid(sid)
if err != nil {
return nil, err
}
- if t != syscall.SidTypeGroup && t != syscall.SidTypeWellKnownGroup && t != syscall.SidTypeAlias {
+ if !isValidGroupAccountType(t) {
return nil, fmt.Errorf("lookupGroupId: should be group account type, not %d", t)
}
return &Group{Name: groupname, Gid: gid}, nil
if err != nil {
return nil, err
}
- username, domain, err := lookupUsernameAndDomain(sid)
+ username, domain, _, err := lookupUsernameAndDomain(sid)
if err != nil {
return nil, err
}
t.Errorf("%+v.GroupIds() = %v; does not contain user GID %s", user, gids, user.Gid)
}
}
+
+var serviceAccounts = []struct {
+ sid string
+ name string
+}{
+ {"S-1-5-18", "NT AUTHORITY\\SYSTEM"},
+ {"S-1-5-19", "NT AUTHORITY\\LOCAL SERVICE"},
+ {"S-1-5-20", "NT AUTHORITY\\NETWORK SERVICE"},
+}
+
+func TestLookupServiceAccount(t *testing.T) {
+ t.Parallel()
+ for _, tt := range serviceAccounts {
+ u, err := Lookup(tt.name)
+ if err != nil {
+ t.Errorf("Lookup(%q): %v", tt.name, err)
+ continue
+ }
+ if u.Uid != tt.sid {
+ t.Errorf("unexpected uid for %q; got %q, want %q", u.Name, u.Uid, tt.sid)
+ }
+ }
+}
+
+func TestLookupIdServiceAccount(t *testing.T) {
+ t.Parallel()
+ for _, tt := range serviceAccounts {
+ u, err := LookupId(tt.sid)
+ if err != nil {
+ t.Errorf("LookupId(%q): %v", tt.sid, err)
+ continue
+ }
+ if u.Gid != tt.sid {
+ t.Errorf("unexpected gid for %q; got %q, want %q", u.Name, u.Gid, tt.sid)
+ }
+ if u.Username != tt.name {
+ t.Errorf("unexpected user name for %q; got %q, want %q", u.Gid, u.Username, tt.name)
+ }
+ }
+}
+
+func TestLookupGroupServiceAccount(t *testing.T) {
+ t.Parallel()
+ for _, tt := range serviceAccounts {
+ u, err := LookupGroup(tt.name)
+ if err != nil {
+ t.Errorf("LookupGroup(%q): %v", tt.name, err)
+ continue
+ }
+ if u.Gid != tt.sid {
+ t.Errorf("unexpected gid for %q; got %q, want %q", u.Name, u.Gid, tt.sid)
+ }
+ }
+}
+
+func TestLookupGroupIdServiceAccount(t *testing.T) {
+ t.Parallel()
+ for _, tt := range serviceAccounts {
+ u, err := LookupGroupId(tt.sid)
+ if err != nil {
+ t.Errorf("LookupGroupId(%q): %v", tt.sid, err)
+ continue
+ }
+ if u.Gid != tt.sid {
+ t.Errorf("unexpected gid for %q; got %q, want %q", u.Name, u.Gid, tt.sid)
+ }
+ }
+}