Skip to content

Commit ae8708f

Browse files
committed
os/user: fix Current().GroupIds() for AD joined users on Windows
This CL special-case User.GroupIds to get the group IDs from the user's token when the user is the current user. This approach is more efficient than calling NetUserGetLocalGroups. It is also more reliable for users joined to an Active Directory domain, where NetUserGetLocalGroups is likely to fail. Updates #26041. Fixes #62712. Cq-Include-Trybots: luci.golang.try:gotip-windows-arm64 Change-Id: If7c30287192872077b98a514bd6346dbd1a64fb4 Reviewed-on: https://go-review.googlesource.com/c/go/+/611116 Reviewed-by: Carlos Amedee <[email protected]> Reviewed-by: Alex Brainman <[email protected]> Reviewed-by: Tim King <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 2927aa1 commit ae8708f

File tree

2 files changed

+74
-11
lines changed

2 files changed

+74
-11
lines changed

src/internal/syscall/windows/security_windows.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,38 @@ func GetUserName(format uint32) (string, error) {
175175
}
176176
}
177177
}
178+
179+
// getTokenInfo retrieves a specified type of information about an access token.
180+
func getTokenInfo(t syscall.Token, class uint32, initSize int) (unsafe.Pointer, error) {
181+
n := uint32(initSize)
182+
for {
183+
b := make([]byte, n)
184+
e := syscall.GetTokenInformation(t, class, &b[0], uint32(len(b)), &n)
185+
if e == nil {
186+
return unsafe.Pointer(&b[0]), nil
187+
}
188+
if e != syscall.ERROR_INSUFFICIENT_BUFFER {
189+
return nil, e
190+
}
191+
if n <= uint32(len(b)) {
192+
return nil, e
193+
}
194+
}
195+
}
196+
197+
type TOKEN_GROUPS struct {
198+
GroupCount uint32
199+
Groups [1]SID_AND_ATTRIBUTES
200+
}
201+
202+
func (g *TOKEN_GROUPS) AllGroups() []SID_AND_ATTRIBUTES {
203+
return (*[(1 << 28) - 1]SID_AND_ATTRIBUTES)(unsafe.Pointer(&g.Groups[0]))[:g.GroupCount:g.GroupCount]
204+
}
205+
206+
func GetTokenGroups(t syscall.Token) (*TOKEN_GROUPS, error) {
207+
i, e := getTokenInfo(t, syscall.TokenGroups, 50)
208+
if e != nil {
209+
return nil, e
210+
}
211+
return (*TOKEN_GROUPS)(i), nil
212+
}

src/os/user/lookup_windows.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -434,17 +434,45 @@ func lookupGroupId(gid string) (*Group, error) {
434434
}
435435

436436
func listGroups(user *User) ([]string, error) {
437-
sid, err := syscall.StringToSid(user.Uid)
438-
if err != nil {
439-
return nil, err
440-
}
441-
username, domain, err := lookupUsernameAndDomain(sid)
442-
if err != nil {
443-
return nil, err
444-
}
445-
sids, err := listGroupsForUsernameAndDomain(username, domain)
446-
if err != nil {
447-
return nil, err
437+
var sids []string
438+
if u, err := Current(); err == nil && u.Uid == user.Uid {
439+
// It is faster and more reliable to get the groups
440+
// of the current user from the current process token.
441+
err := runAsProcessOwner(func() error {
442+
t, err := syscall.OpenCurrentProcessToken()
443+
if err != nil {
444+
return err
445+
}
446+
defer t.Close()
447+
groups, err := windows.GetTokenGroups(t)
448+
if err != nil {
449+
return err
450+
}
451+
for _, g := range groups.AllGroups() {
452+
sid, err := g.Sid.String()
453+
if err != nil {
454+
return err
455+
}
456+
sids = append(sids, sid)
457+
}
458+
return nil
459+
})
460+
if err != nil {
461+
return nil, err
462+
}
463+
} else {
464+
sid, err := syscall.StringToSid(user.Uid)
465+
if err != nil {
466+
return nil, err
467+
}
468+
username, domain, err := lookupUsernameAndDomain(sid)
469+
if err != nil {
470+
return nil, err
471+
}
472+
sids, err = listGroupsForUsernameAndDomain(username, domain)
473+
if err != nil {
474+
return nil, err
475+
}
448476
}
449477
// Add the primary group of the user to the list if it is not already there.
450478
// This is done only to comply with the POSIX concept of a primary group.

0 commit comments

Comments
 (0)