diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index 997a830994..dc5639812a 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -2215,6 +2215,8 @@ var dwarfToName = map[string]string{ "long long unsigned int": "ulonglong", "signed char": "schar", "unsigned char": "uchar", + "unsigned long": "ulong", // Used by Clang 14; issue 53013. + "unsigned long long": "ulonglong", // Used by Clang 14; issue 53013. } const signedDelta = 64 diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go index b36db67a50..17ec87788a 100644 --- a/src/cmd/compile/internal/noder/noder.go +++ b/src/cmd/compile/internal/noder/noder.go @@ -323,7 +323,9 @@ func (p *noder) processPragmas() { } n := ir.AsNode(typecheck.Lookup(l.local).Def) if n == nil || n.Op() != ir.ONAME { - p.errorAt(l.pos, "//go:linkname must refer to declared function or variable") + if types.AllowsGoVersion(types.LocalPkg, 1, 18) { + p.errorAt(l.pos, "//go:linkname must refer to declared function or variable") + } continue } if n.Sym().Linkname != "" { diff --git a/src/cmd/compile/internal/test/race.go b/src/cmd/compile/internal/test/race.go new file mode 100644 index 0000000000..4cc4d53609 --- /dev/null +++ b/src/cmd/compile/internal/test/race.go @@ -0,0 +1,65 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !compiler_bootstrap +// +build !compiler_bootstrap + +package test + +// The racecompile builder only builds packages, but does not build +// or run tests. This is a non-test file to hold cases that (used +// to) trigger compiler data races, so they will be exercised on +// the racecompile builder. +// +// This package is not imported so functions here are not included +// in the actual compiler. + +// Issue 55357: data race when building multiple instantiations of +// generic closures with _ parameters. +func Issue55357() { + type U struct { + A int + B string + C string + } + var q T55357[U] + q.Count() + q.List() + + type M struct { + A int64 + B uint32 + C uint32 + } + var q2 T55357[M] + q2.Count() + q2.List() +} + +type T55357[T any] struct{} + +//go:noinline +func (q *T55357[T]) do(w, v bool, fn func(bk []byte, v T) error) error { + return nil +} + +func (q *T55357[T]) Count() (n int, rerr error) { + err := q.do(false, false, func(kb []byte, _ T) error { + n++ + return nil + }) + return n, err +} + +func (q *T55357[T]) List() (list []T, rerr error) { + var l []T + err := q.do(false, true, func(_ []byte, v T) error { + l = append(l, v) + return nil + }) + if err != nil { + return nil, err + } + return l, nil +} diff --git a/src/cmd/compile/internal/typecheck/subr.go b/src/cmd/compile/internal/typecheck/subr.go index a41a3d62fb..7c48fb577d 100644 --- a/src/cmd/compile/internal/typecheck/subr.go +++ b/src/cmd/compile/internal/typecheck/subr.go @@ -1356,7 +1356,8 @@ func (ts *Tsubster) tstruct(t *types.Type, force bool) *types.Type { newfields[i].SetNointerface(true) } if f.Nname != nil && ts.Vars != nil { - v := ts.Vars[f.Nname.(*ir.Name)] + n := f.Nname.(*ir.Name) + v := ts.Vars[n] if v != nil { // This is the case where we are // translating the type of the function we @@ -1364,6 +1365,13 @@ func (ts *Tsubster) tstruct(t *types.Type, force bool) *types.Type { // the subst.ts.vars table, and we want to // change to reference the new dcl. newfields[i].Nname = v + } else if ir.IsBlank(n) { + // Blank variable is not dcl list. Make a + // new one to not share. + m := ir.NewNameAt(n.Pos(), ir.BlankNode.Sym()) + m.SetType(n.Type()) + m.SetTypecheck(1) + newfields[i].Nname = m } else { // This is the case where we are // translating the type of a function diff --git a/src/cmd/internal/moddeps/moddeps_test.go b/src/cmd/internal/moddeps/moddeps_test.go index 56c3b2585c..cd4d523e46 100644 --- a/src/cmd/internal/moddeps/moddeps_test.go +++ b/src/cmd/internal/moddeps/moddeps_test.go @@ -34,6 +34,8 @@ import ( // See issues 36852, 41409, and 43687. // (Also see golang.org/issue/27348.) func TestAllDependencies(t *testing.T) { + t.Skip("TODO(#57008): 1.18.9 contains unreleased changes from vendored modules") + goBin := testenv.GoToolPath(t) // Ensure that all packages imported within GOROOT diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go index 100a8ff0f9..c3ef503b27 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -540,8 +540,8 @@ func testVerify(t *testing.T, test verifyTest, useSystemRoots bool) { func TestGoVerify(t *testing.T) { // Temporarily enable SHA-1 verification since a number of test chains // require it. TODO(filippo): regenerate test chains. - defer func(old bool) { debugAllowSHA1 = old }(debugAllowSHA1) - debugAllowSHA1 = true + t.Setenv("GODEBUG", "x509sha1=1") + for _, test := range verifyTests { t.Run(test.name, func(t *testing.T) { testVerify(t, test, false) diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index 85720b3ccb..dc470f53f2 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -730,9 +730,6 @@ type Certificate struct { // involves algorithms that are not currently implemented. var ErrUnsupportedAlgorithm = errors.New("x509: cannot verify signature: algorithm unimplemented") -// debugAllowSHA1 allows SHA-1 signatures. See issue 41682. -var debugAllowSHA1 = godebug.Get("x509sha1") == "1" - // An InsecureAlgorithmError indicates that the SignatureAlgorithm used to // generate the signature is not secure, and the signature has been rejected. // @@ -792,7 +789,7 @@ func (c *Certificate) CheckSignatureFrom(parent *Certificate) error { // TODO(agl): don't ignore the path length constraint. - return checkSignature(c.SignatureAlgorithm, c.RawTBSCertificate, c.Signature, parent.PublicKey, debugAllowSHA1) + return checkSignature(c.SignatureAlgorithm, c.RawTBSCertificate, c.Signature, parent.PublicKey, false) } // CheckSignature verifies that signature is a valid signature over signed from @@ -839,7 +836,8 @@ func checkSignature(algo SignatureAlgorithm, signed, signature []byte, publicKey case crypto.MD5: return InsecureAlgorithmError(algo) case crypto.SHA1: - if !allowSHA1 { + // SHA-1 signatures are mostly disabled. See go.dev/issue/41682. + if !allowSHA1 && godebug.Get("x509sha1") != "1" { return InsecureAlgorithmError(algo) } fallthrough diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go index 6c1e95a203..48a864057d 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -1864,9 +1864,7 @@ func TestSHA1(t *testing.T) { t.Fatalf("certificate verification returned %v (%T), wanted InsecureAlgorithmError", err, err) } - defer func(old bool) { debugAllowSHA1 = old }(debugAllowSHA1) - debugAllowSHA1 = true - + t.Setenv("GODEBUG", "x509sha1=1") if err = cert.CheckSignatureFrom(cert); err != nil { t.Fatalf("SHA-1 certificate did not verify with GODEBUG=x509sha1=1: %v", err) } @@ -3335,8 +3333,7 @@ func TestLargeOID(t *testing.T) { } func TestDisableSHA1ForCertOnly(t *testing.T) { - defer func(old bool) { debugAllowSHA1 = old }(debugAllowSHA1) - debugAllowSHA1 = false + t.Setenv("GODEBUG", "") tmpl := &Certificate{ SerialNumber: big.NewInt(1), diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index ed8ddcb307..2a218007d1 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -168,6 +168,7 @@ var depsRules = ` io/fs < internal/testlog < internal/poll + < internal/safefilepath < os < os/signal; diff --git a/src/internal/safefilepath/path.go b/src/internal/safefilepath/path.go new file mode 100644 index 0000000000..0f0a270c30 --- /dev/null +++ b/src/internal/safefilepath/path.go @@ -0,0 +1,21 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package safefilepath manipulates operating-system file paths. +package safefilepath + +import ( + "errors" +) + +var errInvalidPath = errors.New("invalid path") + +// FromFS converts a slash-separated path into an operating-system path. +// +// FromFS returns an error if the path cannot be represented by the operating +// system. For example, paths containing '\' and ':' characters are rejected +// on Windows. +func FromFS(path string) (string, error) { + return fromFS(path) +} diff --git a/src/internal/safefilepath/path_other.go b/src/internal/safefilepath/path_other.go new file mode 100644 index 0000000000..f93da18680 --- /dev/null +++ b/src/internal/safefilepath/path_other.go @@ -0,0 +1,23 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !windows + +package safefilepath + +import "runtime" + +func fromFS(path string) (string, error) { + if runtime.GOOS == "plan9" { + if len(path) > 0 && path[0] == '#' { + return path, errInvalidPath + } + } + for i := range path { + if path[i] == 0 { + return "", errInvalidPath + } + } + return path, nil +} diff --git a/src/internal/safefilepath/path_test.go b/src/internal/safefilepath/path_test.go new file mode 100644 index 0000000000..dc662c18b3 --- /dev/null +++ b/src/internal/safefilepath/path_test.go @@ -0,0 +1,88 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package safefilepath_test + +import ( + "internal/safefilepath" + "os" + "path/filepath" + "runtime" + "testing" +) + +type PathTest struct { + path, result string +} + +const invalid = "" + +var fspathtests = []PathTest{ + {".", "."}, + {"/a/b/c", "/a/b/c"}, + {"a\x00b", invalid}, +} + +var winreservedpathtests = []PathTest{ + {`a\b`, `a\b`}, + {`a:b`, `a:b`}, + {`a/b:c`, `a/b:c`}, + {`NUL`, `NUL`}, + {`./com1`, `./com1`}, + {`a/nul/b`, `a/nul/b`}, +} + +// Whether a reserved name with an extension is reserved or not varies by +// Windows version. +var winreservedextpathtests = []PathTest{ + {"nul.txt", "nul.txt"}, + {"a/nul.txt/b", "a/nul.txt/b"}, +} + +var plan9reservedpathtests = []PathTest{ + {`#c`, `#c`}, +} + +func TestFromFS(t *testing.T) { + switch runtime.GOOS { + case "windows": + if canWriteFile(t, "NUL") { + t.Errorf("can unexpectedly write a file named NUL on Windows") + } + if canWriteFile(t, "nul.txt") { + fspathtests = append(fspathtests, winreservedextpathtests...) + } else { + winreservedpathtests = append(winreservedpathtests, winreservedextpathtests...) + } + for i := range winreservedpathtests { + winreservedpathtests[i].result = invalid + } + for i := range fspathtests { + fspathtests[i].result = filepath.FromSlash(fspathtests[i].result) + } + case "plan9": + for i := range plan9reservedpathtests { + plan9reservedpathtests[i].result = invalid + } + } + tests := fspathtests + tests = append(tests, winreservedpathtests...) + tests = append(tests, plan9reservedpathtests...) + for _, test := range tests { + got, err := safefilepath.FromFS(test.path) + if (got == "") != (err != nil) { + t.Errorf(`FromFS(%q) = %q, %v; want "" only if err != nil`, test.path, got, err) + } + if got != test.result { + t.Errorf("FromFS(%q) = %q, %v; want %q", test.path, got, err, test.result) + } + } +} + +func canWriteFile(t *testing.T, name string) bool { + path := filepath.Join(t.TempDir(), name) + os.WriteFile(path, []byte("ok"), 0666) + b, _ := os.ReadFile(path) + return string(b) == "ok" +} diff --git a/src/internal/safefilepath/path_windows.go b/src/internal/safefilepath/path_windows.go new file mode 100644 index 0000000000..909c150edc --- /dev/null +++ b/src/internal/safefilepath/path_windows.go @@ -0,0 +1,95 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package safefilepath + +import ( + "syscall" + "unicode/utf8" +) + +func fromFS(path string) (string, error) { + if !utf8.ValidString(path) { + return "", errInvalidPath + } + for len(path) > 1 && path[0] == '/' && path[1] == '/' { + path = path[1:] + } + containsSlash := false + for p := path; p != ""; { + // Find the next path element. + i := 0 + dot := -1 + for i < len(p) && p[i] != '/' { + switch p[i] { + case 0, '\\', ':': + return "", errInvalidPath + case '.': + if dot < 0 { + dot = i + } + } + i++ + } + part := p[:i] + if i < len(p) { + containsSlash = true + p = p[i+1:] + } else { + p = "" + } + // Trim the extension and look for a reserved name. + base := part + if dot >= 0 { + base = part[:dot] + } + if isReservedName(base) { + if dot < 0 { + return "", errInvalidPath + } + // The path element is a reserved name with an extension. + // Some Windows versions consider this a reserved name, + // while others do not. Use FullPath to see if the name is + // reserved. + if p, _ := syscall.FullPath(part); len(p) >= 4 && p[:4] == `\\.\` { + return "", errInvalidPath + } + } + } + if containsSlash { + // We can't depend on strings, so substitute \ for / manually. + buf := []byte(path) + for i, b := range buf { + if b == '/' { + buf[i] = '\\' + } + } + path = string(buf) + } + return path, nil +} + +// isReservedName reports if name is a Windows reserved device name. +// It does not detect names with an extension, which are also reserved on some Windows versions. +// +// For details, search for PRN in +// https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file. +func isReservedName(name string) bool { + if 3 <= len(name) && len(name) <= 4 { + switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) { + case "CON", "PRN", "AUX", "NUL": + return len(name) == 3 + case "COM", "LPT": + return len(name) == 4 && '1' <= name[3] && name[3] <= '9' + } + } + return false +} + +func toUpper(c byte) byte { + if 'a' <= c && c <= 'z' { + return c - ('a' - 'A') + } + return c +} diff --git a/src/net/http/fs.go b/src/net/http/fs.go index 6caee9ed93..583203043f 100644 --- a/src/net/http/fs.go +++ b/src/net/http/fs.go @@ -9,6 +9,7 @@ package http import ( "errors" "fmt" + "internal/safefilepath" "io" "io/fs" "mime" @@ -69,14 +70,15 @@ func mapOpenError(originalErr error, name string, sep rune, stat func(string) (f // Open implements FileSystem using os.Open, opening files for reading rooted // and relative to the directory d. func (d Dir) Open(name string) (File, error) { - if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) { - return nil, errors.New("http: invalid character in file path") + path, err := safefilepath.FromFS(path.Clean("/" + name)) + if err != nil { + return nil, errors.New("http: invalid or unsafe file path") } dir := string(d) if dir == "" { dir = "." } - fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))) + fullName := filepath.Join(dir, path) f, err := os.Open(fullName) if err != nil { return nil, mapOpenError(err, fullName, filepath.Separator, os.Stat) diff --git a/src/net/http/fs_test.go b/src/net/http/fs_test.go index d627dfd4be..323360d550 100644 --- a/src/net/http/fs_test.go +++ b/src/net/http/fs_test.go @@ -648,6 +648,34 @@ func TestFileServerZeroByte(t *testing.T) { } } +func TestFileServerNamesEscape(t *testing.T) { + t.Run("h1", func(t *testing.T) { + testFileServerNamesEscape(t, h1Mode) + }) + t.Run("h2", func(t *testing.T) { + testFileServerNamesEscape(t, h2Mode) + }) +} +func testFileServerNamesEscape(t *testing.T, h2 bool) { + defer afterTest(t) + ts := newClientServerTest(t, h2, FileServer(Dir("testdata"))).ts + defer ts.Close() + for _, path := range []string{ + "/../testdata/file", + "/NUL", // don't read from device files on Windows + } { + res, err := ts.Client().Get(ts.URL + path) + if err != nil { + t.Fatal(err) + } + res.Body.Close() + if res.StatusCode < 400 || res.StatusCode > 599 { + t.Errorf("Get(%q): got status %v, want 4xx or 5xx", path, res.StatusCode) + } + + } +} + type fakeFileInfo struct { dir bool basename string diff --git a/src/net/http/h2_bundle.go b/src/net/http/h2_bundle.go index 292dded28d..381f91c8ad 100644 --- a/src/net/http/h2_bundle.go +++ b/src/net/http/h2_bundle.go @@ -3384,10 +3384,11 @@ func (s http2SettingID) String() string { // name (key). See httpguts.ValidHeaderName for the base rules. // // Further, http2 says: -// "Just as in HTTP/1.x, header field names are strings of ASCII -// characters that are compared in a case-insensitive -// fashion. However, header field names MUST be converted to -// lowercase prior to their encoding in HTTP/2. " +// +// "Just as in HTTP/1.x, header field names are strings of ASCII +// characters that are compared in a case-insensitive +// fashion. However, header field names MUST be converted to +// lowercase prior to their encoding in HTTP/2. " func http2validWireHeaderFieldName(v string) bool { if len(v) == 0 { return false @@ -3578,8 +3579,8 @@ func (s *http2sorter) SortStrings(ss []string) { // validPseudoPath reports whether v is a valid :path pseudo-header // value. It must be either: // -// *) a non-empty string starting with '/' -// *) the string '*', for OPTIONS requests. +// *) a non-empty string starting with '/' +// *) the string '*', for OPTIONS requests. // // For now this is only used a quick check for deciding when to clean // up Opaque URLs before sending requests from the Transport. @@ -4242,6 +4243,7 @@ type http2serverConn struct { headerTableSize uint32 peerMaxHeaderListSize uint32 // zero means unknown (default) canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case + canonHeaderKeysSize int // canonHeader keys size in bytes writingFrame bool // started writing a frame (on serve goroutine or separate) writingFrameAsync bool // started a frame on its own goroutine but haven't heard back on wroteFrameCh needsFrameFlush bool // last frame write wasn't a flush @@ -4421,6 +4423,13 @@ func (sc *http2serverConn) condlogf(err error, format string, args ...interface{ } } +// maxCachedCanonicalHeadersKeysSize is an arbitrarily-chosen limit on the size +// of the entries in the canonHeader cache. +// This should be larger than the size of unique, uncommon header keys likely to +// be sent by the peer, while not so high as to permit unreasonable memory usage +// if the peer sends an unbounded number of unique header keys. +const http2maxCachedCanonicalHeadersKeysSize = 2048 + func (sc *http2serverConn) canonicalHeader(v string) string { sc.serveG.check() http2buildCommonHeaderMapsOnce() @@ -4436,14 +4445,10 @@ func (sc *http2serverConn) canonicalHeader(v string) string { sc.canonHeader = make(map[string]string) } cv = CanonicalHeaderKey(v) - // maxCachedCanonicalHeaders is an arbitrarily-chosen limit on the number of - // entries in the canonHeader cache. This should be larger than the number - // of unique, uncommon header keys likely to be sent by the peer, while not - // so high as to permit unreaasonable memory usage if the peer sends an unbounded - // number of unique header keys. - const maxCachedCanonicalHeaders = 32 - if len(sc.canonHeader) < maxCachedCanonicalHeaders { + size := 100 + len(v)*2 // 100 bytes of map overhead + key + value + if sc.canonHeaderKeysSize+size <= http2maxCachedCanonicalHeadersKeysSize { sc.canonHeader[v] = cv + sc.canonHeaderKeysSize += size } return cv } diff --git a/src/net/lookup_test.go b/src/net/lookup_test.go index 3a31f56bea..655543c7d2 100644 --- a/src/net/lookup_test.go +++ b/src/net/lookup_test.go @@ -71,6 +71,10 @@ var lookupGoogleSRVTests = []struct { var backoffDuration = [...]time.Duration{time.Second, 5 * time.Second, 30 * time.Second} func TestLookupGoogleSRV(t *testing.T) { + // TODO(mknyszek): Figure out next steps for this test. This is just + // a quick fix. + t.Skip("fails consistently due to an upstream DNS change; see #56707.") + t.Parallel() mustHaveExternalNetwork(t) @@ -631,6 +635,10 @@ func TestLookupDotsWithLocalSource(t *testing.T) { } func TestLookupDotsWithRemoteSource(t *testing.T) { + // TODO(mknyszek): Figure out next steps for this test. This is just + // a quick fix. + t.Skip("fails consistently due to an upstream DNS change; see #56707.") + if runtime.GOOS == "darwin" || runtime.GOOS == "ios" { testenv.SkipFlaky(t, 27992) } diff --git a/src/os/exec/env_test.go b/src/os/exec/env_test.go index 47b7c04705..43d14fb56d 100644 --- a/src/os/exec/env_test.go +++ b/src/os/exec/env_test.go @@ -12,6 +12,7 @@ import ( func TestDedupEnv(t *testing.T) { tests := []struct { noCase bool + nulOK bool in []string want []string wantErr bool @@ -36,9 +37,15 @@ func TestDedupEnv(t *testing.T) { want: []string{"B=b"}, wantErr: true, }, + { + // Plan 9 needs to preserve environment variables with NUL (#56544). + nulOK: true, + in: []string{"path=one\x00two"}, + want: []string{"path=one\x00two"}, + }, } for _, tt := range tests { - got, err := dedupEnvCase(tt.noCase, tt.in) + got, err := dedupEnvCase(tt.noCase, tt.nulOK, tt.in) if !reflect.DeepEqual(got, tt.want) || (err != nil) != tt.wantErr { t.Errorf("Dedup(%v, %q) = %q, %v; want %q, error:%v", tt.noCase, tt.in, got, err, tt.want, tt.wantErr) } diff --git a/src/os/exec/exec.go b/src/os/exec/exec.go index 056a7e07d7..e1147b75fa 100644 --- a/src/os/exec/exec.go +++ b/src/os/exec/exec.go @@ -745,23 +745,27 @@ func minInt(a, b int) int { // dedupEnv returns a copy of env with any duplicates removed, in favor of // later values. // Items not of the normal environment "key=value" form are preserved unchanged. -// Items containing NUL characters are removed, and an error is returned along with -// the remaining values. +// Except on Plan 9, items containing NUL characters are removed, and +// an error is returned along with the remaining values. func dedupEnv(env []string) ([]string, error) { - return dedupEnvCase(runtime.GOOS == "windows", env) + return dedupEnvCase(runtime.GOOS == "windows", runtime.GOOS == "plan9", env) } // dedupEnvCase is dedupEnv with a case option for testing. // If caseInsensitive is true, the case of keys is ignored. -func dedupEnvCase(caseInsensitive bool, env []string) ([]string, error) { +// If nulOK is false, items containing NUL characters are allowed. +func dedupEnvCase(caseInsensitive, nulOK bool, env []string) ([]string, error) { var err error out := make([]string, 0, len(env)) saw := make(map[string]int, len(env)) // key => index into out for _, kv := range env { - if strings.IndexByte(kv, 0) != -1 { + // Reject NUL in environment variables to prevent security issues (#56284); + // except on Plan 9, which uses NUL as os.PathListSeparator (#56544). + if !nulOK && strings.IndexByte(kv, 0) != -1 { err = errors.New("exec: environment variable contains NUL") continue } + k, _, ok := strings.Cut(kv, "=") if !ok { out = append(out, kv) diff --git a/src/os/exec/exec_test.go b/src/os/exec/exec_test.go index 0be8c6cc18..99bc0329d1 100644 --- a/src/os/exec/exec_test.go +++ b/src/os/exec/exec_test.go @@ -1030,6 +1030,9 @@ func TestDedupEnvEcho(t *testing.T) { } func TestEnvNULCharacter(t *testing.T) { + if runtime.GOOS == "plan9" { + t.Skip("plan9 explicitly allows NUL in the enviroment") + } cmd := helperCommand(t, "echoenv", "FOO", "BAR") cmd.Env = append(cmd.Env, "FOO=foo\x00BAR=bar") out, err := cmd.CombinedOutput() diff --git a/src/os/file.go b/src/os/file.go index 2823128554..8afc6e3540 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -37,12 +37,12 @@ // Note: The maximum number of concurrent operations on a File may be limited by // the OS or the system. The number should be high, but exceeding it may degrade // performance or cause other issues. -// package os import ( "errors" "internal/poll" + "internal/safefilepath" "internal/testlog" "internal/unsafeheader" "io" @@ -623,6 +623,8 @@ func isWindowsNulName(name string) bool { // the /prefix tree, then using DirFS does not stop the access any more than using // os.Open does. DirFS is therefore not a general substitute for a chroot-style security // mechanism when the directory tree contains arbitrary content. +// +// The directory dir must not be "". func DirFS(dir string) fs.FS { return dirFS(dir) } @@ -641,10 +643,11 @@ func containsAny(s, chars string) bool { type dirFS string func (dir dirFS) Open(name string) (fs.File, error) { - if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) { - return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid} + fullname, err := dir.join(name) + if err != nil { + return nil, &PathError{Op: "stat", Path: name, Err: err} } - f, err := Open(string(dir) + "/" + name) + f, err := Open(fullname) if err != nil { return nil, err // nil fs.File } @@ -652,16 +655,35 @@ func (dir dirFS) Open(name string) (fs.File, error) { } func (dir dirFS) Stat(name string) (fs.FileInfo, error) { - if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) { - return nil, &PathError{Op: "stat", Path: name, Err: ErrInvalid} + fullname, err := dir.join(name) + if err != nil { + return nil, &PathError{Op: "stat", Path: name, Err: err} } - f, err := Stat(string(dir) + "/" + name) + f, err := Stat(fullname) if err != nil { return nil, err } return f, nil } +// join returns the path for name in dir. +func (dir dirFS) join(name string) (string, error) { + if dir == "" { + return "", errors.New("os: DirFS with empty root") + } + if !fs.ValidPath(name) { + return "", ErrInvalid + } + name, err := safefilepath.FromFS(name) + if err != nil { + return "", ErrInvalid + } + if IsPathSeparator(dir[len(dir)-1]) { + return string(dir) + name, nil + } + return string(dir) + string(PathSeparator) + name, nil +} + // ReadFile reads the named file and returns the contents. // A successful call returns err == nil, not err == EOF. // Because ReadFile reads the whole file, it does not treat an EOF from Read diff --git a/src/os/os_test.go b/src/os/os_test.go index 63427deb6e..4124be13cc 100644 --- a/src/os/os_test.go +++ b/src/os/os_test.go @@ -2696,6 +2696,44 @@ func TestDirFS(t *testing.T) { if err == nil { t.Fatalf(`Open testdata\dirfs succeeded`) } + + // Test that Open does not open Windows device files. + _, err = d.Open(`NUL`) + if err == nil { + t.Errorf(`Open NUL succeeded`) + } +} + +func TestDirFSRootDir(t *testing.T) { + cwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + cwd = cwd[len(filepath.VolumeName(cwd)):] // trim volume prefix (C:) on Windows + cwd = filepath.ToSlash(cwd) // convert \ to / + cwd = strings.TrimPrefix(cwd, "/") // trim leading / + + // Test that Open can open a path starting at /. + d := DirFS("/") + f, err := d.Open(cwd + "/testdata/dirfs/a") + if err != nil { + t.Fatal(err) + } + f.Close() +} + +func TestDirFSEmptyDir(t *testing.T) { + d := DirFS("") + cwd, _ := os.Getwd() + for _, path := range []string{ + "testdata/dirfs/a", // not DirFS(".") + filepath.ToSlash(cwd) + "/testdata/dirfs/a", // not DirFS("/") + } { + _, err := d.Open(path) + if err == nil { + t.Fatalf(`DirFS("").Open(%q) succeeded`, path) + } + } } func TestDirFSPathsValid(t *testing.T) { diff --git a/src/runtime/malloc.go b/src/runtime/malloc.go index 6ed6ceade2..d738644c7e 100644 --- a/src/runtime/malloc.go +++ b/src/runtime/malloc.go @@ -1135,6 +1135,16 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { // the garbage collector could follow a pointer to x, // but see uninitialized memory or stale heap bits. publicationBarrier() + // As x and the heap bits are initialized, update + // freeIndexForScan now so x is seen by the GC + // (including convervative scan) as an allocated object. + // While this pointer can't escape into user code as a + // _live_ pointer until we return, conservative scanning + // may find a dead pointer that happens to point into this + // object. Delaying this update until now ensures that + // conservative scanning considers this pointer dead until + // this point. + span.freeIndexForScan = span.freeindex // Allocate black during GC. // All slots hold nil so no scanning is needed. diff --git a/src/runtime/mbitmap.go b/src/runtime/mbitmap.go index 937968807b..95d88d8c61 100644 --- a/src/runtime/mbitmap.go +++ b/src/runtime/mbitmap.go @@ -220,7 +220,7 @@ func (s *mspan) nextFreeIndex() uintptr { // been no preemption points since ensuring this (which could allow a // GC transition, which would allow the state to change). func (s *mspan) isFree(index uintptr) bool { - if index < s.freeindex { + if index < s.freeIndexForScan { return false } bytep, mask := s.allocBits.bitp(index) diff --git a/src/runtime/mgcsweep.go b/src/runtime/mgcsweep.go index 0d58f8e0b5..2aa670e1b8 100644 --- a/src/runtime/mgcsweep.go +++ b/src/runtime/mgcsweep.go @@ -623,6 +623,7 @@ func (sl *sweepLocked) sweep(preserve bool) bool { s.allocCount = nalloc s.freeindex = 0 // reset allocation index to start of span. + s.freeIndexForScan = 0 if trace.enabled { getg().m.p.ptr().traceReclaimed += uintptr(nfreed) * s.elemsize } diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go index ecbd0a3a49..134387562e 100644 --- a/src/runtime/mheap.go +++ b/src/runtime/mheap.go @@ -459,6 +459,14 @@ type mspan struct { limit uintptr // end of data in span speciallock mutex // guards specials list specials *special // linked list of special records sorted by offset. + + // freeIndexForScan is like freeindex, except that freeindex is + // used by the allocator whereas freeIndexForScan is used by the + // GC scanner. They are two fields so that the GC sees the object + // is allocated only when the object and the heap bits are + // initialized (see also the assignment of freeIndexForScan in + // mallocgc, and issue 54596). + freeIndexForScan uintptr } func (s *mspan) base() uintptr { @@ -1250,6 +1258,7 @@ HaveSpan: // Initialize mark and allocation structures. s.freeindex = 0 + s.freeIndexForScan = 0 s.allocCache = ^uint64(0) // all 1s indicating all free. s.gcmarkBits = newMarkBits(s.nelems) s.allocBits = newAllocBits(s.nelems) @@ -1565,6 +1574,7 @@ func (span *mspan) init(base uintptr, npages uintptr) { span.specials = nil span.needzero = 0 span.freeindex = 0 + span.freeIndexForScan = 0 span.allocBits = nil span.gcmarkBits = nil span.state.set(mSpanDead) diff --git a/src/runtime/sys_linux_ppc64x.s b/src/runtime/sys_linux_ppc64x.s index 01d6c85597..b86a820ab2 100644 --- a/src/runtime/sys_linux_ppc64x.s +++ b/src/runtime/sys_linux_ppc64x.s @@ -119,16 +119,23 @@ TEXT runtimeĀ·pipe2(SB),NOSPLIT|NOFRAME,$0-20 MOVW R3, errno+16(FP) RET +// func usleep(usec uint32) TEXT runtimeĀ·usleep(SB),NOSPLIT,$16-4 MOVW usec+0(FP), R3 - MOVD R3, R5 - MOVW $1000000, R4 - DIVD R4, R3 - MOVD R3, 8(R1) - MOVW $1000, R4 - MULLD R3, R4 - SUB R4, R5 - MOVD R5, 16(R1) + + // Use magic constant 0x8637bd06 and shift right 51 + // to perform usec/1000000. + ORIS $0x8637, R0, R4 // Note, R0 always contains 0 here. + OR $0xbd06, R4, R4 + MULLD R3, R4, R4 // Convert usec to S. + SRD $51, R4, R4 + MOVD R4, 8(R1) // Store to tv_sec + + MOVD $1000000, R5 + MULLW R4, R5, R5 // Convert tv_sec back into uS + SUB R5, R3, R5 // Compute remainder uS. + MULLD $1000, R5, R5 // Convert to nsec + MOVD R5, 16(R1) // Store to tv_nsec // nanosleep(&ts, 0) ADD $8, R1, R3 diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index 56128dc882..b8f580f084 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -182,6 +182,17 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in case funcID_systemstack: // systemstack returns normally, so just follow the // stack transition. + if usesLR && funcspdelta(f, frame.pc, &cache) == 0 { + // We're at the function prologue and the stack + // switch hasn't happened, or epilogue where we're + // about to return. Just unwind normally. + // Do this only on LR machines because on x86 + // systemstack doesn't have an SP delta (the CALL + // instruction opens the frame), therefore no way + // to check. + flag &^= funcFlag_SPWRITE + break + } frame.sp = gp.m.curg.sched.sp stack = gp.m.curg.stack cgoCtxt = gp.m.curg.cgoCtxt diff --git a/test/fixedbugs/issue55889.go b/test/fixedbugs/issue55889.go new file mode 100644 index 0000000000..68812c2157 --- /dev/null +++ b/test/fixedbugs/issue55889.go @@ -0,0 +1,21 @@ +// errorcheck -0 -lang=go1.17 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Prior to Go 1.18, ineffectual //go:linkname directives were treated +// as noops. Ensure that modules that contain these directives (e.g., +// x/sys prior to go.dev/cl/274573) continue to compile. + +package p + +import _ "unsafe" + +//go:linkname nonexistent nonexistent + +//go:linkname constant constant +const constant = 42 + +//go:linkname typename typename +type typename int