mirror of
https://github.com/golang/go
synced 2024-11-22 05:04:40 -07:00
os/user: support built-in service user accounts on Windows
Built-in service user accounts should be treated as special cases of well-known groups and allowed in user.Lookup and user.LookupId. Namely, these accounts are: - NT AUTHORITY\SYSTEM (S-1-5-18) - NT AUTHORITY\LOCAL SERVICE (S-1-5-19) - NT AUTHORITY\NETWORK SERVICE (S-1-5-20) See https://learn.microsoft.com/en-us/windows/win32/services/service-user-accounts. Note that #49509 also mentions S-1-5-17 (NT AUTHORITY\IUSR) as another well-known group that should be treated as a user. I haven't found any documentation supporting this claim, and it is not an account that is used usually, so I'm not adding it for now. This CL is heavily based on CL 452497. Fixes #49509 Change-Id: I6e204ddfb4ed0c01b4503001cf284602531e4a88 Reviewed-on: https://go-review.googlesource.com/c/go/+/626255 Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Alex Brainman <alex.brainman@gmail.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
parent
dea12628e6
commit
60d66e6a86
5
doc/next/6-stdlib/99-minor/os/user/49509.md
Normal file
5
doc/next/6-stdlib/99-minor/os/user/49509.md
Normal file
@ -0,0 +1,5 @@
|
||||
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`
|
@ -210,3 +210,27 @@ func GetTokenGroups(t syscall.Token) (*TOKEN_GROUPS, error) {
|
||||
}
|
||||
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
|
||||
|
@ -49,8 +49,12 @@ var (
|
||||
|
||||
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")
|
||||
@ -120,6 +124,24 @@ func DuplicateTokenEx(hExistingToken syscall.Token, dwDesiredAccess uint32, lpTo
|
||||
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 {
|
||||
@ -136,6 +158,12 @@ func ImpersonateSelf(impersonationlevel uint32) (err error) {
|
||||
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 {
|
||||
|
@ -86,16 +86,73 @@ func getProfilesDirectory() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@ -118,13 +175,7 @@ func lookupGroupName(groupname string) (string, error) {
|
||||
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()
|
||||
@ -355,11 +406,7 @@ func lookupUserPrimaryGroup(username, domain string) (string, error) {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -367,6 +414,19 @@ func newUserFromSid(usid *syscall.SID) (*User, error) {
|
||||
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
|
||||
@ -396,7 +456,7 @@ func lookupUser(username string) (*User, error) {
|
||||
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)
|
||||
@ -427,7 +487,7 @@ func lookupGroupId(gid string) (*Group, error) {
|
||||
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
|
||||
@ -465,7 +525,7 @@ func listGroups(user *User) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
username, domain, err := lookupUsernameAndDomain(sid)
|
||||
username, domain, _, err := lookupUsernameAndDomain(sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -202,3 +202,71 @@ func TestGroupIdsTestUser(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user