mirror of
https://github.com/golang/go.git
synced 2026-02-01 08:32:04 +03:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac94297758 | ||
|
|
6961c3775f | ||
|
|
ebee011a54 | ||
|
|
84fb1b8253 | ||
|
|
c95d3093ca | ||
|
|
561964c9a8 | ||
|
|
e73dadc758 | ||
|
|
2899144b8d | ||
|
|
b062eb46e8 | ||
|
|
8ac5714ef2 | ||
|
|
9546293d22 | ||
|
|
4b3a0b9785 | ||
|
|
5abb1d84f8 |
@@ -1 +1,2 @@
|
||||
branch: master
|
||||
branch: release-branch.go1.25
|
||||
parent-branch: master
|
||||
|
||||
@@ -277,6 +277,29 @@ func loadModTool(ctx context.Context, name string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func builtTool(runAction *work.Action) string {
|
||||
linkAction := runAction.Deps[0]
|
||||
if toolN {
|
||||
// #72824: If -n is set, use the cached path if we can.
|
||||
// This is only necessary if the binary wasn't cached
|
||||
// before this invocation of the go command: if the binary
|
||||
// was cached, BuiltTarget() will be the cached executable.
|
||||
// It's only in the "first run", where we actually do the build
|
||||
// and save the result to the cache that BuiltTarget is not
|
||||
// the cached binary. Ideally, we would set BuiltTarget
|
||||
// to the cached path even in the first run, but if we
|
||||
// copy the binary to the cached path, and try to run it
|
||||
// in the same process, we'll run into the dreaded #22315
|
||||
// resulting in occasional ETXTBSYs. Instead of getting the
|
||||
// ETXTBSY and then retrying just don't use the cached path
|
||||
// on the first run if we're going to actually run the binary.
|
||||
if cached := linkAction.CachedExecutable(); cached != "" {
|
||||
return cached
|
||||
}
|
||||
}
|
||||
return linkAction.BuiltTarget()
|
||||
}
|
||||
|
||||
func buildAndRunBuiltinTool(ctx context.Context, toolName, tool string, args []string) {
|
||||
// Override GOOS and GOARCH for the build to build the tool using
|
||||
// the same GOOS and GOARCH as this go command.
|
||||
@@ -288,7 +311,7 @@ func buildAndRunBuiltinTool(ctx context.Context, toolName, tool string, args []s
|
||||
modload.RootMode = modload.NoRoot
|
||||
|
||||
runFunc := func(b *work.Builder, ctx context.Context, a *work.Action) error {
|
||||
cmdline := str.StringList(a.Deps[0].BuiltTarget(), a.Args)
|
||||
cmdline := str.StringList(builtTool(a), a.Args)
|
||||
return runBuiltTool(toolName, nil, cmdline)
|
||||
}
|
||||
|
||||
@@ -300,7 +323,7 @@ func buildAndRunModtool(ctx context.Context, toolName, tool string, args []strin
|
||||
// Use the ExecCmd to run the binary, as go run does. ExecCmd allows users
|
||||
// to provide a runner to run the binary, for example a simulator for binaries
|
||||
// that are cross-compiled to a different platform.
|
||||
cmdline := str.StringList(work.FindExecCmd(), a.Deps[0].BuiltTarget(), a.Args)
|
||||
cmdline := str.StringList(work.FindExecCmd(), builtTool(a), a.Args)
|
||||
// Use same environment go run uses to start the executable:
|
||||
// the original environment with cfg.GOROOTbin added to the path.
|
||||
env := slices.Clip(cfg.OrigEnv)
|
||||
|
||||
@@ -97,11 +97,12 @@ type Action struct {
|
||||
CacheExecutable bool // Whether to cache executables produced by link steps
|
||||
|
||||
// Generated files, directories.
|
||||
Objdir string // directory for intermediate objects
|
||||
Target string // goal of the action: the created package or executable
|
||||
built string // the actual created package or executable
|
||||
actionID cache.ActionID // cache ID of action input
|
||||
buildID string // build ID of action output
|
||||
Objdir string // directory for intermediate objects
|
||||
Target string // goal of the action: the created package or executable
|
||||
built string // the actual created package or executable
|
||||
cachedExecutable string // the cached executable, if CacheExecutable was set
|
||||
actionID cache.ActionID // cache ID of action input
|
||||
buildID string // build ID of action output
|
||||
|
||||
VetxOnly bool // Mode=="vet": only being called to supply info about dependencies
|
||||
needVet bool // Mode=="build": need to fill in vet config
|
||||
@@ -133,6 +134,10 @@ func (a *Action) BuildID() string { return a.buildID }
|
||||
// from Target when the result was cached.
|
||||
func (a *Action) BuiltTarget() string { return a.built }
|
||||
|
||||
// CachedExecutable returns the cached executable, if CacheExecutable
|
||||
// was set and the executable could be cached, and "" otherwise.
|
||||
func (a *Action) CachedExecutable() string { return a.cachedExecutable }
|
||||
|
||||
// An actionQueue is a priority queue of actions.
|
||||
type actionQueue []*Action
|
||||
|
||||
|
||||
@@ -745,8 +745,9 @@ func (b *Builder) updateBuildID(a *Action, target string) error {
|
||||
}
|
||||
outputID, _, err := c.PutExecutable(a.actionID, name+cfg.ExeSuffix, r)
|
||||
r.Close()
|
||||
a.cachedExecutable = c.OutputFile(outputID)
|
||||
if err == nil && cfg.BuildX {
|
||||
sh.ShowCmd("", "%s # internal", joinUnambiguously(str.StringList("cp", target, c.OutputFile(outputID))))
|
||||
sh.ShowCmd("", "%s # internal", joinUnambiguously(str.StringList("cp", target, a.cachedExecutable)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
src/cmd/go/testdata/script/tool_n_issue72824.txt
vendored
Normal file
27
src/cmd/go/testdata/script/tool_n_issue72824.txt
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
[short] skip 'does a build in using an empty cache'
|
||||
|
||||
# Start with a fresh cache because we want to verify the behavior
|
||||
# when the tool hasn't been cached previously.
|
||||
env GOCACHE=$WORK${/}cache
|
||||
|
||||
# Even when the tool hasn't been previously cached but was built and
|
||||
# saved to the cache in the invocation of 'go tool -n' we should return
|
||||
# its cached location.
|
||||
go tool -n foo
|
||||
stdout $GOCACHE
|
||||
|
||||
# And of course we should also return the cached location on subsequent
|
||||
# runs.
|
||||
go tool -n foo
|
||||
stdout $GOCACHE
|
||||
|
||||
-- go.mod --
|
||||
module example.com/foo
|
||||
|
||||
go 1.25
|
||||
|
||||
tool example.com/foo
|
||||
-- main.go --
|
||||
package main
|
||||
|
||||
func main() {}
|
||||
@@ -335,7 +335,6 @@ func convertAssignRows(dest, src any, rows *Rows) error {
|
||||
if rows == nil {
|
||||
return errors.New("invalid context to convert cursor rows, missing parent *Rows")
|
||||
}
|
||||
rows.closemu.Lock()
|
||||
*d = Rows{
|
||||
dc: rows.dc,
|
||||
releaseConn: func(error) {},
|
||||
@@ -351,7 +350,6 @@ func convertAssignRows(dest, src any, rows *Rows) error {
|
||||
parentCancel()
|
||||
}
|
||||
}
|
||||
rows.closemu.Unlock()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -91,8 +91,6 @@ func (cc *fakeDriverCtx) OpenConnector(name string) (driver.Connector, error) {
|
||||
type fakeDB struct {
|
||||
name string
|
||||
|
||||
useRawBytes atomic.Bool
|
||||
|
||||
mu sync.Mutex
|
||||
tables map[string]*table
|
||||
badConn bool
|
||||
@@ -684,8 +682,6 @@ func (c *fakeConn) PrepareContext(ctx context.Context, query string) (driver.Stm
|
||||
switch cmd {
|
||||
case "WIPE":
|
||||
// Nothing
|
||||
case "USE_RAWBYTES":
|
||||
c.db.useRawBytes.Store(true)
|
||||
case "SELECT":
|
||||
stmt, err = c.prepareSelect(stmt, parts)
|
||||
case "CREATE":
|
||||
@@ -789,9 +785,6 @@ func (s *fakeStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (d
|
||||
case "WIPE":
|
||||
db.wipe()
|
||||
return driver.ResultNoRows, nil
|
||||
case "USE_RAWBYTES":
|
||||
s.c.db.useRawBytes.Store(true)
|
||||
return driver.ResultNoRows, nil
|
||||
case "CREATE":
|
||||
if err := db.createTable(s.table, s.colName, s.colType); err != nil {
|
||||
return nil, err
|
||||
@@ -1076,10 +1069,9 @@ type rowsCursor struct {
|
||||
errPos int
|
||||
err error
|
||||
|
||||
// a clone of slices to give out to clients, indexed by the
|
||||
// original slice's first byte address. we clone them
|
||||
// just so we're able to corrupt them on close.
|
||||
bytesClone map[*byte][]byte
|
||||
// Data returned to clients.
|
||||
// We clone and stash it here so it can be invalidated by Close and Next.
|
||||
driverOwnedMemory [][]byte
|
||||
|
||||
// Every operation writes to line to enable the race detector
|
||||
// check for data races.
|
||||
@@ -1096,9 +1088,19 @@ func (rc *rowsCursor) touchMem() {
|
||||
rc.line++
|
||||
}
|
||||
|
||||
func (rc *rowsCursor) invalidateDriverOwnedMemory() {
|
||||
for _, buf := range rc.driverOwnedMemory {
|
||||
for i := range buf {
|
||||
buf[i] = 'x'
|
||||
}
|
||||
}
|
||||
rc.driverOwnedMemory = nil
|
||||
}
|
||||
|
||||
func (rc *rowsCursor) Close() error {
|
||||
rc.touchMem()
|
||||
rc.parentMem.touchMem()
|
||||
rc.invalidateDriverOwnedMemory()
|
||||
rc.closed = true
|
||||
return rc.closeErr
|
||||
}
|
||||
@@ -1129,6 +1131,8 @@ func (rc *rowsCursor) Next(dest []driver.Value) error {
|
||||
if rc.posRow >= len(rc.rows[rc.posSet]) {
|
||||
return io.EOF // per interface spec
|
||||
}
|
||||
// Corrupt any previously returned bytes.
|
||||
rc.invalidateDriverOwnedMemory()
|
||||
for i, v := range rc.rows[rc.posSet][rc.posRow].cols {
|
||||
// TODO(bradfitz): convert to subset types? naah, I
|
||||
// think the subset types should only be input to
|
||||
@@ -1136,20 +1140,13 @@ func (rc *rowsCursor) Next(dest []driver.Value) error {
|
||||
// a wider range of types coming out of drivers. all
|
||||
// for ease of drivers, and to prevent drivers from
|
||||
// messing up conversions or doing them differently.
|
||||
dest[i] = v
|
||||
|
||||
if bs, ok := v.([]byte); ok && !rc.db.useRawBytes.Load() {
|
||||
if rc.bytesClone == nil {
|
||||
rc.bytesClone = make(map[*byte][]byte)
|
||||
}
|
||||
clone, ok := rc.bytesClone[&bs[0]]
|
||||
if !ok {
|
||||
clone = make([]byte, len(bs))
|
||||
copy(clone, bs)
|
||||
rc.bytesClone[&bs[0]] = clone
|
||||
}
|
||||
dest[i] = clone
|
||||
if bs, ok := v.([]byte); ok {
|
||||
// Clone []bytes and stash for later invalidation.
|
||||
bs = bytes.Clone(bs)
|
||||
rc.driverOwnedMemory = append(rc.driverOwnedMemory, bs)
|
||||
v = bs
|
||||
}
|
||||
dest[i] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3368,38 +3368,36 @@ func (rs *Rows) Scan(dest ...any) error {
|
||||
// without calling Next.
|
||||
return fmt.Errorf("sql: Scan called without calling Next (closemuScanHold)")
|
||||
}
|
||||
|
||||
rs.closemu.RLock()
|
||||
|
||||
if rs.lasterr != nil && rs.lasterr != io.EOF {
|
||||
rs.closemu.RUnlock()
|
||||
return rs.lasterr
|
||||
}
|
||||
if rs.closed {
|
||||
err := rs.lasterrOrErrLocked(errRowsClosed)
|
||||
rs.closemu.RUnlock()
|
||||
return err
|
||||
}
|
||||
|
||||
if scanArgsContainRawBytes(dest) {
|
||||
rs.raw = rs.raw[:0]
|
||||
err := rs.scanLocked(dest...)
|
||||
if err == nil && scanArgsContainRawBytes(dest) {
|
||||
rs.closemuScanHold = true
|
||||
rs.raw = rs.raw[:0]
|
||||
} else {
|
||||
rs.closemu.RUnlock()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (rs *Rows) scanLocked(dest ...any) error {
|
||||
if rs.lasterr != nil && rs.lasterr != io.EOF {
|
||||
return rs.lasterr
|
||||
}
|
||||
if rs.closed {
|
||||
return rs.lasterrOrErrLocked(errRowsClosed)
|
||||
}
|
||||
|
||||
if rs.lastcols == nil {
|
||||
rs.closemuRUnlockIfHeldByScan()
|
||||
return errors.New("sql: Scan called without calling Next")
|
||||
}
|
||||
if len(dest) != len(rs.lastcols) {
|
||||
rs.closemuRUnlockIfHeldByScan()
|
||||
return fmt.Errorf("sql: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest))
|
||||
}
|
||||
|
||||
for i, sv := range rs.lastcols {
|
||||
err := convertAssignRows(dest[i], sv, rs)
|
||||
if err != nil {
|
||||
rs.closemuRUnlockIfHeldByScan()
|
||||
return fmt.Errorf(`sql: Scan error on column index %d, name %q: %w`, i, rs.rowsi.Columns()[i], err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
@@ -4434,10 +4435,6 @@ func testContextCancelDuringRawBytesScan(t *testing.T, mode string) {
|
||||
db := newTestDB(t, "people")
|
||||
defer closeDB(t, db)
|
||||
|
||||
if _, err := db.Exec("USE_RAWBYTES"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// cancel used to call close asynchronously.
|
||||
// This test checks that it waits so as not to interfere with RawBytes.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@@ -4529,6 +4526,61 @@ func TestContextCancelBetweenNextAndErr(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type testScanner struct {
|
||||
scanf func(src any) error
|
||||
}
|
||||
|
||||
func (ts testScanner) Scan(src any) error { return ts.scanf(src) }
|
||||
|
||||
func TestContextCancelDuringScan(t *testing.T) {
|
||||
db := newTestDB(t, "people")
|
||||
defer closeDB(t, db)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
scanStart := make(chan any)
|
||||
scanEnd := make(chan error)
|
||||
scanner := &testScanner{
|
||||
scanf: func(src any) error {
|
||||
scanStart <- src
|
||||
return <-scanEnd
|
||||
},
|
||||
}
|
||||
|
||||
// Start a query, and pause it mid-scan.
|
||||
want := []byte("Alice")
|
||||
r, err := db.QueryContext(ctx, "SELECT|people|name|name=?", string(want))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !r.Next() {
|
||||
t.Fatalf("r.Next() = false, want true")
|
||||
}
|
||||
go func() {
|
||||
r.Scan(scanner)
|
||||
}()
|
||||
got := <-scanStart
|
||||
defer close(scanEnd)
|
||||
gotBytes, ok := got.([]byte)
|
||||
if !ok {
|
||||
t.Fatalf("r.Scan returned %T, want []byte", got)
|
||||
}
|
||||
if !bytes.Equal(gotBytes, want) {
|
||||
t.Fatalf("before cancel: r.Scan returned %q, want %q", gotBytes, want)
|
||||
}
|
||||
|
||||
// Cancel the query.
|
||||
// Sleep to give it a chance to finish canceling.
|
||||
cancel()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Cancelling the query should not have changed the result.
|
||||
if !bytes.Equal(gotBytes, want) {
|
||||
t.Fatalf("after cancel: r.Scan result is now %q, want %q", gotBytes, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilErrorAfterClose(t *testing.T) {
|
||||
db := newTestDB(t, "people")
|
||||
defer closeDB(t, db)
|
||||
@@ -4562,10 +4614,6 @@ func TestRawBytesReuse(t *testing.T) {
|
||||
db := newTestDB(t, "people")
|
||||
defer closeDB(t, db)
|
||||
|
||||
if _, err := db.Exec("USE_RAWBYTES"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var raw RawBytes
|
||||
|
||||
// The RawBytes in this query aliases driver-owned memory.
|
||||
|
||||
@@ -177,4 +177,48 @@ func TestLookPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
checker := func(test string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Logf("PATH=%s", os.Getenv("PATH"))
|
||||
p, err := LookPath(test)
|
||||
if err == nil {
|
||||
t.Errorf("%q: error expected, got nil", test)
|
||||
}
|
||||
if p != "" {
|
||||
t.Errorf("%q: path returned should be \"\". Got %q", test, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reference behavior for the next test
|
||||
t.Run(pathVar+"=$OTHER2", func(t *testing.T) {
|
||||
t.Run("empty", checker(""))
|
||||
t.Run("dot", checker("."))
|
||||
t.Run("dotdot1", checker("abc/.."))
|
||||
t.Run("dotdot2", checker(".."))
|
||||
})
|
||||
|
||||
// Test the behavior when PATH contains an executable file which is not a directory
|
||||
t.Run(pathVar+"=exe", func(t *testing.T) {
|
||||
// Inject an executable file (not a directory) in PATH.
|
||||
// Use our own binary os.Args[0].
|
||||
t.Setenv(pathVar, testenv.Executable(t))
|
||||
t.Run("empty", checker(""))
|
||||
t.Run("dot", checker("."))
|
||||
t.Run("dotdot1", checker("abc/.."))
|
||||
t.Run("dotdot2", checker(".."))
|
||||
})
|
||||
|
||||
// Test the behavior when PATH contains an executable file which is not a directory
|
||||
t.Run(pathVar+"=exe/xx", func(t *testing.T) {
|
||||
// Inject an executable file (not a directory) in PATH.
|
||||
// Use our own binary os.Args[0].
|
||||
t.Setenv(pathVar, filepath.Join(testenv.Executable(t), "xx"))
|
||||
t.Run("empty", checker(""))
|
||||
t.Run("dot", checker("."))
|
||||
t.Run("dotdot1", checker("abc/.."))
|
||||
t.Run("dotdot2", checker(".."))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1328,3 +1328,13 @@ func addCriticalEnv(env []string) []string {
|
||||
// Code should use errors.Is(err, ErrDot), not err == ErrDot,
|
||||
// to test whether a returned error err is due to this condition.
|
||||
var ErrDot = errors.New("cannot run executable found relative to current directory")
|
||||
|
||||
// validateLookPath excludes paths that can't be valid
|
||||
// executable names. See issue #74466 and CVE-2025-47906.
|
||||
func validateLookPath(s string) error {
|
||||
switch s {
|
||||
case "", ".", "..":
|
||||
return ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ func findExecutable(file string) error {
|
||||
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
|
||||
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
|
||||
func LookPath(file string) (string, error) {
|
||||
if err := validateLookPath(file); err != nil {
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
|
||||
// skip the path lookup for these prefixes
|
||||
skip := []string{"/", "#", "./", "../"}
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ func LookPath(file string) (string, error) {
|
||||
// (only bypass the path if file begins with / or ./ or ../)
|
||||
// but that would not match all the Unix shells.
|
||||
|
||||
if err := validateLookPath(file); err != nil {
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
|
||||
if strings.Contains(file, "/") {
|
||||
err := findExecutable(file)
|
||||
if err == nil {
|
||||
|
||||
@@ -67,6 +67,10 @@ func findExecutable(file string, exts []string) (string, error) {
|
||||
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
|
||||
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
|
||||
func LookPath(file string) (string, error) {
|
||||
if err := validateLookPath(file); err != nil {
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
|
||||
return lookPath(file, pathExt())
|
||||
}
|
||||
|
||||
@@ -80,6 +84,10 @@ func LookPath(file string) (string, error) {
|
||||
// "C:\foo\example.com" would be returned as-is even if the
|
||||
// program is actually "C:\foo\example.com.exe".
|
||||
func lookExtensions(path, dir string) (string, error) {
|
||||
if err := validateLookPath(path); err != nil {
|
||||
return "", &Error{path, err}
|
||||
}
|
||||
|
||||
if filepath.Base(path) == path {
|
||||
path = "." + string(filepath.Separator) + path
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package user
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/syscall/windows"
|
||||
@@ -16,11 +17,92 @@ import (
|
||||
"runtime"
|
||||
"slices"
|
||||
"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.
|
||||
@@ -32,47 +114,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)
|
||||
@@ -80,13 +130,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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user