From: qmuntal Date: Thu, 24 Jul 2025 13:38:35 +0000 (+0200) Subject: [release-branch.go1.24] os/user: user random name for the test user account X-Git-Tag: go1.24.6~4 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=731de13dc37dead19bb02917447d1cd8991f7eda;p=gostls13.git [release-branch.go1.24] os/user: user random name for the test user account TestImpersonated and TestGroupIdsTestUser are flaky due to sporadic failures when creating the test user account when running the tests from different processes at the same time. This flakiness can be fixed by using a random name for the test user account. For #73523. Fixes #74760. Cq-Include-Trybots: luci.golang.try:go1.24-windows-amd64-longtest Change-Id: Ib2283a888437420502b1c11d876c975f5af4bc03 Reviewed-on: https://go-review.googlesource.com/c/go/+/690175 Auto-Submit: Quim Muntal Reviewed-by: Michael Pratt Reviewed-by: Dmitri Shuralyov TryBot-Bypass: Dmitri Shuralyov (cherry picked from commit 374e3be2eb9b546ef1340f750e343c15a8f87dde) Reviewed-on: https://go-review.googlesource.com/c/go/+/690556 Reviewed-by: Mark Freeman Reviewed-by: Quim Muntal LUCI-TryBot-Result: Go LUCI Auto-Submit: Dmitri Shuralyov --- diff --git a/src/os/user/user_windows_test.go b/src/os/user/user_windows_test.go index c71503372e..c8ee10cbb9 100644 --- a/src/os/user/user_windows_test.go +++ b/src/os/user/user_windows_test.go @@ -7,6 +7,7 @@ package user import ( "crypto/rand" "encoding/base64" + "encoding/binary" "errors" "fmt" "internal/syscall/windows" @@ -15,11 +16,92 @@ import ( "os/exec" "runtime" "strconv" + "strings" "syscall" "testing" + "unicode" + "unicode/utf8" "unsafe" ) +// addUserAccount creates a local user account. +// It returns the name and password of the new account. +// Multiple programs or goroutines calling addUserAccount simultaneously will not choose the same directory. +func addUserAccount(t *testing.T) (name, password string) { + t.TempDir() + pattern := t.Name() + // Windows limits the user name to 20 characters, + // leave space for a 4 digits random suffix. + const maxNameLen, suffixLen = 20, 4 + pattern = pattern[:min(len(pattern), maxNameLen-suffixLen)] + // Drop unusual characters from the account name. + mapper := func(r rune) rune { + if r < utf8.RuneSelf { + if '0' <= r && r <= '9' || + 'a' <= r && r <= 'z' || + 'A' <= r && r <= 'Z' { + return r + } + } else if unicode.IsLetter(r) || unicode.IsNumber(r) { + return r + } + return -1 + } + pattern = strings.Map(mapper, pattern) + + // Generate a long random password. + var pwd [33]byte + rand.Read(pwd[:]) + // Add special chars to ensure it satisfies password requirements. + password = base64.StdEncoding.EncodeToString(pwd[:]) + "_-As@!%*(1)4#2" + password16, err := syscall.UTF16PtrFromString(password) + if err != nil { + t.Fatal(err) + } + + try := 0 + for { + // Calculate a random suffix to append to the user name. + var suffix [2]byte + rand.Read(suffix[:]) + suffixStr := strconv.FormatUint(uint64(binary.LittleEndian.Uint16(suffix[:])), 10) + name := pattern + suffixStr[:min(len(suffixStr), suffixLen)] + name16, err := syscall.UTF16PtrFromString(name) + if err != nil { + t.Fatal(err) + } + // Create user. + userInfo := windows.UserInfo1{ + Name: name16, + Password: password16, + Priv: windows.USER_PRIV_USER, + } + err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil) + if errors.Is(err, syscall.ERROR_ACCESS_DENIED) { + t.Skip("skipping test; don't have permission to create user") + } + // If the user already exists, try again with a different name. + if errors.Is(err, windows.NERR_UserExists) { + if try++; try < 1000 { + t.Log("user already exists, trying again with a different name") + continue + } + } + if err != nil { + t.Fatalf("NetUserAdd failed: %v", err) + } + // Delete the user when the test is done. + t.Cleanup(func() { + if err := windows.NetUserDel(nil, name16); err != nil { + if !errors.Is(err, windows.NERR_UserNotFound) { + t.Fatal(err) + } + } + }) + return name, password + } +} + // windowsTestAccount creates a test user and returns a token for that user. // If the user already exists, it will be deleted and recreated. // The caller is responsible for closing the token. @@ -31,47 +113,15 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) { // See https://dev.go/issue/70396. t.Skip("skipping non-hermetic test outside of Go builders") } - const testUserName = "GoStdTestUser01" - var password [33]byte - rand.Read(password[:]) - // Add special chars to ensure it satisfies password requirements. - pwd := base64.StdEncoding.EncodeToString(password[:]) + "_-As@!%*(1)4#2" - name, err := syscall.UTF16PtrFromString(testUserName) + name, password := addUserAccount(t) + name16, err := syscall.UTF16PtrFromString(name) if err != nil { t.Fatal(err) } - pwd16, err := syscall.UTF16PtrFromString(pwd) + pwd16, err := syscall.UTF16PtrFromString(password) if err != nil { t.Fatal(err) } - userInfo := windows.UserInfo1{ - Name: name, - Password: pwd16, - Priv: windows.USER_PRIV_USER, - } - // Create user. - err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil) - if errors.Is(err, syscall.ERROR_ACCESS_DENIED) { - t.Skip("skipping test; don't have permission to create user") - } - if errors.Is(err, windows.NERR_UserExists) { - // User already exists, delete and recreate. - if err = windows.NetUserDel(nil, name); err != nil { - t.Fatal(err) - } - if err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil); err != nil { - t.Fatal(err) - } - } else if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - if err = windows.NetUserDel(nil, name); err != nil { - if !errors.Is(err, windows.NERR_UserNotFound) { - t.Fatal(err) - } - } - }) domain, err := syscall.UTF16PtrFromString(".") if err != nil { t.Fatal(err) @@ -79,13 +129,13 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) { const LOGON32_PROVIDER_DEFAULT = 0 const LOGON32_LOGON_INTERACTIVE = 2 var token syscall.Token - if err = windows.LogonUser(name, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil { + if err = windows.LogonUser(name16, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil { t.Fatal(err) } t.Cleanup(func() { token.Close() }) - usr, err := Lookup(testUserName) + usr, err := Lookup(name) if err != nil { t.Fatal(err) }