Use of unsafe package detected; ensure it’s necessary, justified in comments, and bounds-checked to avoid memory corruption
syscall.NetApiBufferFree((*byte)(unsafe.Pointer(domain)))
1// Copyright 2012 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package user67import (8 "errors"9 "fmt"10 "internal/syscall/windows"11 "internal/syscall/windows/registry"12 "runtime"13 "syscall"14 "unsafe"15)1617func isDomainJoined() (bool, error) {18 var domain *uint1619 var status uint3220 err := syscall.NetGetJoinInformation(nil, &domain, &status)21 if err != nil {22 return false, err23 }24 syscall.NetApiBufferFree((*byte)(unsafe.Pointer(domain)))25 return status == syscall.NetSetupDomainName, nil26}2728func lookupFullNameDomain(domainAndUser string) (string, error) {29 return syscall.TranslateAccountName(domainAndUser,30 syscall.NameSamCompatible, syscall.NameDisplay, 50)31}3233func lookupFullNameServer(servername, username string) (string, error) {34 s, e := syscall.UTF16PtrFromString(servername)35 if e != nil {36 return "", e37 }38 u, e := syscall.UTF16PtrFromString(username)39 if e != nil {40 return "", e41 }42 var p *byte43 e = syscall.NetUserGetInfo(s, u, 10, &p)44 if e != nil {45 return "", e46 }47 defer syscall.NetApiBufferFree(p)48 i := (*syscall.UserInfo10)(unsafe.Pointer(p))49 return windows.UTF16PtrToString(i.FullName), nil50}5152func lookupFullName(domain, username, domainAndUser string) (string, error) {53 joined, err := isDomainJoined()54 if err == nil && joined {55 name, err := lookupFullNameDomain(domainAndUser)56 if err == nil {57 return name, nil58 }59 }60 name, err := lookupFullNameServer(domain, username)61 if err == nil {62 return name, nil63 }64 // domain worked neither as a domain nor as a server65 // could be domain server unavailable66 // pretend username is fullname67 return username, nil68}6970// getProfilesDirectory retrieves the path to the root directory71// where user profiles are stored.72func getProfilesDirectory() (string, error) {73 n := uint32(100)74 for {75 b := make([]uint16, n)76 e := windows.GetProfilesDirectory(&b[0], &n)77 if e == nil {78 return syscall.UTF16ToString(b), nil79 }80 if e != syscall.ERROR_INSUFFICIENT_BUFFER {81 return "", e82 }83 if n <= uint32(len(b)) {84 return "", e85 }86 }87}8889func isServiceAccount(sid *syscall.SID) bool {90 if !windows.IsValidSid(sid) {91 // We don't accept SIDs from the public API, so this should never happen.92 // Better be on the safe side and validate anyway.93 return false94 }95 // The following RIDs are considered service user accounts as per96 // https://learn.microsoft.com/en-us/windows/win32/secauthz/well-known-sids and97 // https://learn.microsoft.com/en-us/windows/win32/services/service-user-accounts:98 // - "S-1-5-18": LocalSystem99 // - "S-1-5-19": LocalService100 // - "S-1-5-20": NetworkService101 if windows.GetSidSubAuthorityCount(sid) != windows.SID_REVISION ||102 windows.GetSidIdentifierAuthority(sid) != windows.SECURITY_NT_AUTHORITY {103 return false104 }105 switch windows.GetSidSubAuthority(sid, 0) {106 case windows.SECURITY_LOCAL_SYSTEM_RID,107 windows.SECURITY_LOCAL_SERVICE_RID,108 windows.SECURITY_NETWORK_SERVICE_RID:109 return true110 }111 return false112}113114func isValidUserAccountType(sid *syscall.SID, sidType uint32) bool {115 switch sidType {116 case syscall.SidTypeUser:117 return true118 case syscall.SidTypeWellKnownGroup:119 return isServiceAccount(sid)120 }121 return false122}123124func isValidGroupAccountType(sidType uint32) bool {125 switch sidType {126 case syscall.SidTypeGroup:127 return true128 case syscall.SidTypeWellKnownGroup:129 // Some well-known groups are also considered service accounts,130 // so isValidUserAccountType would return true for them.131 // We have historically allowed them in LookupGroup and LookupGroupId,132 // so don't treat them as invalid here.133 return true134 case syscall.SidTypeAlias:135 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/7b2aeb27-92fc-41f6-8437-deb65d950921#gt_0387e636-5654-4910-9519-1f8326cf5ec0136 // SidTypeAlias should also be treated as a group type next to SidTypeGroup137 // and SidTypeWellKnownGroup:138 // "alias object -> resource group: A group object..."139 //140 // Tests show that "Administrators" can be considered of type SidTypeAlias.141 return true142 }143 return false144}145146// lookupUsernameAndDomain obtains the username and domain for usid.147func lookupUsernameAndDomain(usid *syscall.SID) (username, domain string, sidType uint32, e error) {148 username, domain, sidType, e = usid.LookupAccount("")149 if e != nil {150 return "", "", 0, e151 }152 if !isValidUserAccountType(usid, sidType) {153 return "", "", 0, fmt.Errorf("user: should be user account type, not %d", sidType)154 }155 return username, domain, sidType, nil156}157158// findHomeDirInRegistry finds the user home path based on the uid.159func findHomeDirInRegistry(uid string) (dir string, e error) {160 k, e := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\`+uid, registry.QUERY_VALUE)161 if e != nil {162 return "", e163 }164 defer k.Close()165 dir, _, e = k.GetStringValue("ProfileImagePath")166 if e != nil {167 return "", e168 }169 return dir, nil170}171172// lookupGroupName accepts the name of a group and retrieves the group SID.173func lookupGroupName(groupname string) (string, error) {174 sid, _, t, e := syscall.LookupSID("", groupname)175 if e != nil {176 if errors.Is(e, windows.ERROR_NONE_MAPPED) {177 return "", fmt.Errorf("%w: %w", UnknownGroupError(groupname), e)178 }179 return "", e180 }181 if !isValidGroupAccountType(t) {182 return "", fmt.Errorf("lookupGroupName: should be group account type, not %d", t)183 }184 return sid.String()185}186187// listGroupsForUsernameAndDomain accepts username and domain and retrieves188// a SID list of the local groups where this user is a member.189func listGroupsForUsernameAndDomain(username, domain string) ([]string, error) {190 // Check if both the domain name and user should be used.191 var query string192 joined, err := isDomainJoined()193 if err == nil && joined && len(domain) != 0 {194 query = domain + `\` + username195 } else {196 query = username197 }198 q, err := syscall.UTF16PtrFromString(query)199 if err != nil {200 return nil, err201 }202 var p0 *byte203 var entriesRead, totalEntries uint32204 // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netusergetlocalgroups205 // NetUserGetLocalGroups() would return a list of LocalGroupUserInfo0206 // elements which hold the names of local groups where the user participates.207 // The list does not follow any sorting order.208 err = windows.NetUserGetLocalGroups(nil, q, 0, windows.LG_INCLUDE_INDIRECT, &p0, windows.MAX_PREFERRED_LENGTH, &entriesRead, &totalEntries)209 if err != nil {210 return nil, err211 }212 defer syscall.NetApiBufferFree(p0)213 if entriesRead == 0 {214 return nil, nil215 }216 entries := (*[1024]windows.LocalGroupUserInfo0)(unsafe.Pointer(p0))[:entriesRead:entriesRead]217 var sids []string218 for _, entry := range entries {219 if entry.Name == nil {220 continue221 }222 sid, err := lookupGroupName(windows.UTF16PtrToString(entry.Name))223 if err != nil {224 return nil, err225 }226 sids = append(sids, sid)227 }228 return sids, nil229}230231func newUser(uid, gid, dir, username, domain string) (*User, error) {232 domainAndUser := domain + `\` + username233 name, e := lookupFullName(domain, username, domainAndUser)234 if e != nil {235 return nil, e236 }237 u := &User{238 Uid: uid,239 Gid: gid,240 Username: domainAndUser,241 Name: name,242 HomeDir: dir,243 }244 return u, nil245}246247var (248 // unused variables (in this implementation)249 // modified during test to exercise code paths in the cgo implementation.250 userBuffer = 0251 groupBuffer = 0252)253254func current() (*User, error) {255 // Use runAsProcessOwner to ensure that we can access the process token256 // when calling syscall.OpenCurrentProcessToken if the current thread257 // is impersonating a different user. See https://go.dev/issue/68647.258 var usr *User259 err := runAsProcessOwner(func() error {260 t, e := syscall.OpenCurrentProcessToken()261 if e != nil {262 return e263 }264 defer t.Close()265 u, e := t.GetTokenUser()266 if e != nil {267 return e268 }269 pg, e := t.GetTokenPrimaryGroup()270 if e != nil {271 return e272 }273 uid, e := u.User.Sid.String()274 if e != nil {275 return e276 }277 gid, e := pg.PrimaryGroup.String()278 if e != nil {279 return e280 }281 dir, e := t.GetUserProfileDirectory()282 if e != nil {283 return e284 }285 username, e := windows.GetUserName(syscall.NameSamCompatible)286 if e != nil {287 return e288 }289 displayName, e := windows.GetUserName(syscall.NameDisplay)290 if e != nil {291 // Historically, the username is used as fallback292 // when the display name can't be retrieved.293 displayName = username294 }295 usr = &User{296 Uid: uid,297 Gid: gid,298 Username: username,299 Name: displayName,300 HomeDir: dir,301 }302 return nil303 })304 return usr, err305}306307// runAsProcessOwner runs f in the context of the current process owner,308// that is, removing any impersonation that may be in effect before calling f,309// and restoring the impersonation afterwards.310func runAsProcessOwner(f func() error) error {311 var impersonationRollbackErr error312 runtime.LockOSThread()313 defer func() {314 // If impersonation failed, the thread is running with the wrong token,315 // so it's better to terminate it.316 // This is achieved by not calling runtime.UnlockOSThread.317 if impersonationRollbackErr != nil {318 println("os/user: failed to revert to previous token:", impersonationRollbackErr.Error())319 runtime.Goexit()320 } else {321 runtime.UnlockOSThread()322 }323 }()324 prevToken, isProcessToken, err := getCurrentToken()325 if err != nil {326 return fmt.Errorf("os/user: failed to get current token: %w", err)327 }328 defer prevToken.Close()329 if !isProcessToken {330 if err = windows.RevertToSelf(); err != nil {331 return fmt.Errorf("os/user: failed to revert to self: %w", err)332 }333 defer func() {334 impersonationRollbackErr = windows.ImpersonateLoggedOnUser(prevToken)335 }()336 }337 return f()338}339340// getCurrentToken returns the current thread token, or341// the process token if the thread doesn't have a token.342func getCurrentToken() (t syscall.Token, isProcessToken bool, err error) {343 thread, _ := windows.GetCurrentThread()344 // Need TOKEN_DUPLICATE and TOKEN_IMPERSONATE to use the token in ImpersonateLoggedOnUser.345 err = windows.OpenThreadToken(thread, syscall.TOKEN_QUERY|syscall.TOKEN_DUPLICATE|syscall.TOKEN_IMPERSONATE, true, &t)346 if errors.Is(err, windows.ERROR_NO_TOKEN) {347 // Not impersonating, use the process token.348 isProcessToken = true349 t, err = syscall.OpenCurrentProcessToken()350 }351 return t, isProcessToken, err352}353354// lookupUserPrimaryGroup obtains the primary group SID for a user using this method:355// https://support.microsoft.com/en-us/help/297951/how-to-use-the-primarygroupid-attribute-to-find-the-primary-group-for356// The method follows this formula: domainRID + "-" + primaryGroupRID357func lookupUserPrimaryGroup(username, domain string) (string, error) {358 // get the domain RID359 sid, _, t, e := syscall.LookupSID("", domain)360 if e != nil {361 return "", e362 }363 if t != syscall.SidTypeDomain {364 return "", fmt.Errorf("lookupUserPrimaryGroup: should be domain account type, not %d", t)365 }366 domainRID, e := sid.String()367 if e != nil {368 return "", e369 }370 // If the user has joined a domain use the RID of the default primary group371 // called "Domain Users":372 // https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems373 // SID: S-1-5-21domain-513374 //375 // The correct way to obtain the primary group of a domain user is376 // probing the user primaryGroupID attribute in the server Active Directory:377 // https://learn.microsoft.com/en-us/windows/win32/adschema/a-primarygroupid378 //379 // Note that the primary group of domain users should not be modified380 // on Windows for performance reasons, even if it's possible to do that.381 // The .NET Developer's Guide to Directory Services Programming - Page 409382 // https://books.google.bg/books?id=kGApqjobEfsC&lpg=PA410&ots=p7oo-eOQL7&dq=primary%20group%20RID&hl=bg&pg=PA409#v=onepage&q&f=false383 joined, err := isDomainJoined()384 if err == nil && joined {385 return domainRID + "-513", nil386 }387 // For non-domain users call NetUserGetInfo() with level 4, which388 // in this case would not have any network overhead.389 // The primary group should not change from RID 513 here either390 // but the group will be called "None" instead:391 // https://www.adampalmer.me/iodigitalsec/2013/08/10/windows-null-session-enumeration/392 // "Group 'None' (RID: 513)"393 u, e := syscall.UTF16PtrFromString(username)394 if e != nil {395 return "", e396 }397 d, e := syscall.UTF16PtrFromString(domain)398 if e != nil {399 return "", e400 }401 var p *byte402 e = syscall.NetUserGetInfo(d, u, 4, &p)403 if e != nil {404 return "", e405 }406 defer syscall.NetApiBufferFree(p)407 i := (*windows.UserInfo4)(unsafe.Pointer(p))408 return fmt.Sprintf("%s-%d", domainRID, i.PrimaryGroupID), nil409}410411func newUserFromSid(usid *syscall.SID) (*User, error) {412 username, domain, sidType, e := lookupUsernameAndDomain(usid)413 if e != nil {414 return nil, e415 }416 uid, e := usid.String()417 if e != nil {418 return nil, e419 }420 var gid string421 if sidType == syscall.SidTypeWellKnownGroup {422 // The SID does not contain a domain; this function's domain variable has423 // been populated with the SID's identifier authority. This happens with424 // special service user accounts such as "NT AUTHORITY\LocalSystem".425 // In this case, gid is the same as the user SID.426 gid = uid427 } else {428 gid, e = lookupUserPrimaryGroup(username, domain)429 if e != nil {430 return nil, e431 }432 }433 // If this user has logged in at least once their home path should be stored434 // in the registry under the specified SID. References:435 // https://social.technet.microsoft.com/wiki/contents/articles/13895.how-to-remove-a-corrupted-user-profile-from-the-registry.aspx436 // https://support.asperasoft.com/hc/en-us/articles/216127438-How-to-delete-Windows-user-profiles437 //438 // The registry is the most reliable way to find the home path as the user439 // might have decided to move it outside of the default location,440 // (e.g. C:\users). Reference:441 // https://answers.microsoft.com/en-us/windows/forum/windows_7-security/how-do-i-set-a-home-directory-outside-cusers-for-a/aed68262-1bf4-4a4d-93dc-7495193a440f442 dir, e := findHomeDirInRegistry(uid)443 if e != nil {444 // If the home path does not exist in the registry, the user might445 // have not logged in yet; fall back to using getProfilesDirectory().446 // Find the username based on a SID and append that to the result of447 // getProfilesDirectory(). The domain is not relevant here.448 dir, e = getProfilesDirectory()449 if e != nil {450 return nil, e451 }452 dir += `\` + username453 }454 return newUser(uid, gid, dir, username, domain)455}456457func lookupUser(username string) (*User, error) {458 sid, _, t, e := syscall.LookupSID("", username)459 if e != nil {460 if errors.Is(e, windows.ERROR_NONE_MAPPED) {461 return nil, fmt.Errorf("%w: %w", UnknownUserError(username), e)462 }463 return nil, e464 }465 if !isValidUserAccountType(sid, t) {466 return nil, fmt.Errorf("user: should be user account type, not %d", t)467 }468 return newUserFromSid(sid)469}470471func lookupUserId(uid string) (*User, error) {472 sid, e := syscall.StringToSid(uid)473 if e != nil {474 return nil, e475 }476 return newUserFromSid(sid)477}478479func lookupGroup(groupname string) (*Group, error) {480 sid, err := lookupGroupName(groupname)481 if err != nil {482 return nil, err483 }484 return &Group{Name: groupname, Gid: sid}, nil485}486487func lookupGroupId(gid string) (*Group, error) {488 sid, err := syscall.StringToSid(gid)489 if err != nil {490 return nil, err491 }492 groupname, _, t, err := sid.LookupAccount("")493 if err != nil {494 return nil, err495 }496 if !isValidGroupAccountType(t) {497 return nil, fmt.Errorf("lookupGroupId: should be group account type, not %d", t)498 }499 return &Group{Name: groupname, Gid: gid}, nil500}501502func listGroups(user *User) ([]string, error) {503 var sids []string504 if u, err := Current(); err == nil && u.Uid == user.Uid {505 // It is faster and more reliable to get the groups506 // of the current user from the current process token.507 err := runAsProcessOwner(func() error {508 t, err := syscall.OpenCurrentProcessToken()509 if err != nil {510 return err511 }512 defer t.Close()513 groups, err := windows.GetTokenGroups(t)514 if err != nil {515 return err516 }517 for _, g := range groups.AllGroups() {518 sid, err := g.Sid.String()519 if err != nil {520 return err521 }522 sids = append(sids, sid)523 }524 return nil525 })526 if err != nil {527 return nil, err528 }529 } else {530 sid, err := syscall.StringToSid(user.Uid)531 if err != nil {532 return nil, err533 }534 username, domain, _, err := lookupUsernameAndDomain(sid)535 if err != nil {536 return nil, err537 }538 sids, err = listGroupsForUsernameAndDomain(username, domain)539 if err != nil {540 return nil, err541 }542 }543 // Add the primary group of the user to the list if it is not already there.544 // This is done only to comply with the POSIX concept of a primary group.545 for _, sid := range sids {546 if sid == user.Gid {547 return sids, nil548 }549 }550 return append(sids, user.Gid), nil551}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.