From 6961c3775f9358d2fe0b72f2626c4485228203d0 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Wed, 23 Jul 2025 14:26:54 -0700 Subject: [PATCH 01/59] [release-branch.go1.25] database/sql: avoid closing Rows while scan is in progress A database/sql/driver.Rows can return database-owned data from Rows.Next. The driver.Rows documentation doesn't explicitly document the lifetime guarantees for this data, but a reasonable expectation is that the caller of Next should only access it until the next call to Rows.Close or Rows.Next. Avoid violating that constraint when a query is cancelled while a call to database/sql.Rows.Scan (note the difference between the two different Rows types!) is in progress. We previously took care to avoid closing a driver.Rows while the user has access to driver-owned memory via a RawData, but we could still close a driver.Rows while a Scan call was in the process of reading previously-returned driver-owned data. Update the fake DB used in database/sql tests to invalidate returned data to help catch other places we might be incorrectly retaining it. Updates #74831 Fixes #74834 Change-Id: Ice45b5fad51b679c38e3e1d21ef39156b56d6037 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2540 Reviewed-by: Roland Shoemaker Reviewed-by: Neal Patel Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2600 Reviewed-on: https://go-review.googlesource.com/c/go/+/693559 Auto-Submit: Dmitri Shuralyov TryBot-Bypass: Dmitri Shuralyov Reviewed-by: Mark Freeman Reviewed-by: Dmitri Shuralyov --- src/database/sql/convert.go | 2 -- src/database/sql/fakedb_test.go | 47 ++++++++++++------------ src/database/sql/sql.go | 26 +++++++------- src/database/sql/sql_test.go | 64 ++++++++++++++++++++++++++++----- 4 files changed, 90 insertions(+), 49 deletions(-) diff --git a/src/database/sql/convert.go b/src/database/sql/convert.go index 65fdfe6fa8c3ad..26b139ababd178 100644 --- a/src/database/sql/convert.go +++ b/src/database/sql/convert.go @@ -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 } } diff --git a/src/database/sql/fakedb_test.go b/src/database/sql/fakedb_test.go index 3dfcd447b52bca..003e6c62986f31 100644 --- a/src/database/sql/fakedb_test.go +++ b/src/database/sql/fakedb_test.go @@ -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 } diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go index b0abcf7fcd408b..4be450ca876877 100644 --- a/src/database/sql/sql.go +++ b/src/database/sql/sql.go @@ -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() + rs.raw = rs.raw[:0] + err := rs.scanLocked(dest...) + if err == nil && scanArgsContainRawBytes(dest) { + rs.closemuScanHold = true + } else { + rs.closemu.RUnlock() + } + return err +} +func (rs *Rows) scanLocked(dest ...any) error { 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.closemuScanHold = true - rs.raw = rs.raw[:0] - } else { - rs.closemu.RUnlock() + 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) } } diff --git a/src/database/sql/sql_test.go b/src/database/sql/sql_test.go index 74b9bf550249c7..4750edd4717561 100644 --- a/src/database/sql/sql_test.go +++ b/src/database/sql/sql_test.go @@ -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. From ac94297758f3d83fca5ffa16cd179bb098bbd914 Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Wed, 6 Aug 2025 11:06:52 -0700 Subject: [PATCH 02/59] [release-branch.go1.25] go1.25rc3 Change-Id: I7801c8fe17b0712b479d45fda0d81c060a904097 Reviewed-on: https://go-review.googlesource.com/c/go/+/693716 TryBot-Bypass: Gopher Robot Auto-Submit: Gopher Robot Reviewed-by: Dmitri Shuralyov Reviewed-by: Mark Freeman --- VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 982968de533cfc..1187a14a5a0ece 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -go1.25rc2 -time 2025-07-02T21:52:16Z +go1.25rc3 +time 2025-08-01T19:24:17Z From 6e676ab2b809d46623acb5988248d95d1eb7939c Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Tue, 12 Aug 2025 13:44:03 -0700 Subject: [PATCH 03/59] [release-branch.go1.25] go1.25.0 Change-Id: I46dcb2de47fd752d61863cc351ad792b64995a93 Reviewed-on: https://go-review.googlesource.com/c/go/+/695416 Auto-Submit: Gopher Robot Reviewed-by: Dmitri Shuralyov TryBot-Bypass: Gopher Robot Commit-Queue: David Chase Reviewed-by: Dmitri Shuralyov Reviewed-by: David Chase --- VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 1187a14a5a0ece..329f88ef126ffd 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -go1.25rc3 -time 2025-08-01T19:24:17Z +go1.25.0 +time 2025-08-08T19:33:32Z From c72fcab6d60d4907984a619d5d69bb9e7e18077a Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Tue, 29 Jul 2025 17:12:24 -0400 Subject: [PATCH 04/59] [release-branch.go1.25] cmd/go/internal/gover: fix ModIsPrerelease for toolchain versions We forgot to call the IsPrerelease function on FromToolchain(vers) rather than on vers itself. IsPrerelase expects a version without the "go" prefix. See the corresponding code in ModIsValid and ModIsPrefix that call FromToolchain before passing the versions to IsValid and IsLang respectively. Fixes #74822 Change-Id: I3cf055e1348e6a9dc0334e414f06fe85eaf78024 Reviewed-on: https://go-review.googlesource.com/c/go/+/691655 LUCI-TryBot-Result: Go LUCI Auto-Submit: Michael Matloob Reviewed-by: Michael Matloob Reviewed-by: Michael Pratt (cherry picked from commit 69338a335ada5882b888fb7eabe0ad6f0e12c986) Reviewed-on: https://go-review.googlesource.com/c/go/+/691958 --- src/cmd/go/internal/gover/mod.go | 3 +++ src/cmd/go/testdata/script/mod_get_toolchain.txt | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/cmd/go/internal/gover/mod.go b/src/cmd/go/internal/gover/mod.go index d3cc17068def6d..3ac5ae8824b233 100644 --- a/src/cmd/go/internal/gover/mod.go +++ b/src/cmd/go/internal/gover/mod.go @@ -109,6 +109,9 @@ func ModIsPrefix(path, vers string) bool { // The caller is assumed to have checked that ModIsValid(path, vers) is true. func ModIsPrerelease(path, vers string) bool { if IsToolchain(path) { + if path == "toolchain" { + return IsPrerelease(FromToolchain(vers)) + } return IsPrerelease(vers) } return semver.Prerelease(vers) != "" diff --git a/src/cmd/go/testdata/script/mod_get_toolchain.txt b/src/cmd/go/testdata/script/mod_get_toolchain.txt index 87e84ae15ef987..83cef4a0fd0c5e 100644 --- a/src/cmd/go/testdata/script/mod_get_toolchain.txt +++ b/src/cmd/go/testdata/script/mod_get_toolchain.txt @@ -94,12 +94,14 @@ stderr '^go: added toolchain go1.24rc1$' grep 'go 1.22.9' go.mod # no longer implied grep 'toolchain go1.24rc1' go.mod -# go get toolchain@latest finds go1.999testmod. +# go get toolchain@latest finds go1.23.9. cp go.mod.orig go.mod go get toolchain@latest -stderr '^go: added toolchain go1.999testmod$' +stderr '^go: added toolchain go1.23.9$' grep 'go 1.21' go.mod -grep 'toolchain go1.999testmod' go.mod +grep 'toolchain go1.23.9' go.mod + + # Bug fixes. @@ -115,7 +117,7 @@ stderr '^go: upgraded go 1.19 => 1.21.0' # go get toolchain@1.24rc1 is OK too. go get toolchain@1.24rc1 -stderr '^go: downgraded toolchain go1.999testmod => go1.24rc1$' +stderr '^go: upgraded toolchain go1.23.9 => go1.24rc1$' # go get go@1.21 should work if we are the Go 1.21 language version, # even though there's no toolchain for it. From 21ac81c1e11338fbb8ef0fb52e73def26860028f Mon Sep 17 00:00:00 2001 From: Richard Miller Date: Thu, 7 Aug 2025 11:15:23 +0100 Subject: [PATCH 05/59] [release-branch.go1.25] os/exec: fix incorrect expansion of ".." in LookPath on plan9 The correction in CL 685755 is incomplete for plan9, where path search is performed even on file strings containing "/". By applying filepath.Clean to the argument of validateLookPath, we can check for bogus file strings containing ".." where the later call to filepath.Join would transform a path like "badfile/dir/.." to "badfile" even where "dir" isn't a directory or doesn't exist. For #74466 Fixes #75008 Change-Id: I3f8b73a1de6bc7d8001b1ca8e74b78722408548e Reviewed-on: https://go-review.googlesource.com/c/go/+/693935 Reviewed-by: David du Colombier <0intro@gmail.com> LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase Reviewed-by: Dmitri Shuralyov (cherry picked from commit 674c5f0edd82b5d1dd5cb44eb4b85830245c151e) Reviewed-on: https://go-review.googlesource.com/c/go/+/698416 Reviewed-by: Cherry Mui --- src/os/exec/lp_plan9.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/os/exec/lp_plan9.go b/src/os/exec/lp_plan9.go index 0430af9eefeb42..f713a6905cfbdc 100644 --- a/src/os/exec/lp_plan9.go +++ b/src/os/exec/lp_plan9.go @@ -36,7 +36,7 @@ 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 { + if err := validateLookPath(filepath.Clean(file)); err != nil { return "", &Error{file, err} } From 749dff880af3e7ac0b41fbc6a41cfb6fff6e80b9 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Thu, 14 Aug 2025 10:27:54 -0700 Subject: [PATCH 06/59] [release-branch.go1.25] runtime: make all synctest bubble violations fatal panics Unblocking a bubbled goroutine from outside the bubble is an error and panics. Currently, some of those panics are regular panics and some are fatal. We use fatal panics in cases where its difficult to panic without leaving something in an inconsistent state. Change the regular panics (channel and timer operations) to be fatal. This makes our behavior more consistent: All bubble violations are always fatal. More importantly, it avoids introducing new, recoverable panics. A motivating example for this change is the context package, which performs channel operations with a mutex held in the expectation that those operations can never panic. These operations can now panic as a result of a bubble violation, potentially leaving a context.Context in an inconsistent state. For #74837 Fixes #75021 Change-Id: Ie6efd916b7f505c0f13dde42de1572992401f15c Reviewed-on: https://go-review.googlesource.com/c/go/+/696195 Auto-Submit: Damien Neil LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt (cherry picked from commit a8564bd412d4495a6048f981d30d4d7abb1e45a7) Reviewed-on: https://go-review.googlesource.com/c/go/+/696196 --- src/internal/synctest/synctest_test.go | 83 +++++++++++++------------- src/runtime/chan.go | 10 ++-- src/runtime/select.go | 2 +- src/runtime/time.go | 4 +- 4 files changed, 51 insertions(+), 48 deletions(-) diff --git a/src/internal/synctest/synctest_test.go b/src/internal/synctest/synctest_test.go index 6cebf86c31f416..307eee62e2b9aa 100644 --- a/src/internal/synctest/synctest_test.go +++ b/src/internal/synctest/synctest_test.go @@ -383,57 +383,59 @@ func TestChannelMovedOutOfBubble(t *testing.T) { for _, test := range []struct { desc string f func(chan struct{}) - wantPanic string + wantFatal string }{{ desc: "receive", f: func(ch chan struct{}) { <-ch }, - wantPanic: "receive on synctest channel from outside bubble", + wantFatal: "receive on synctest channel from outside bubble", }, { desc: "send", f: func(ch chan struct{}) { ch <- struct{}{} }, - wantPanic: "send on synctest channel from outside bubble", + wantFatal: "send on synctest channel from outside bubble", }, { desc: "close", f: func(ch chan struct{}) { close(ch) }, - wantPanic: "close of synctest channel from outside bubble", + wantFatal: "close of synctest channel from outside bubble", }} { t.Run(test.desc, func(t *testing.T) { // Bubbled channel accessed from outside any bubble. t.Run("outside_bubble", func(t *testing.T) { - donec := make(chan struct{}) - ch := make(chan chan struct{}) - go func() { - defer close(donec) - defer wantPanic(t, test.wantPanic) - test.f(<-ch) - }() - synctest.Run(func() { - ch <- make(chan struct{}) + wantFatal(t, test.wantFatal, func() { + donec := make(chan struct{}) + ch := make(chan chan struct{}) + go func() { + defer close(donec) + test.f(<-ch) + }() + synctest.Run(func() { + ch <- make(chan struct{}) + }) + <-donec }) - <-donec }) // Bubbled channel accessed from a different bubble. t.Run("different_bubble", func(t *testing.T) { - donec := make(chan struct{}) - ch := make(chan chan struct{}) - go func() { - defer close(donec) - c := <-ch + wantFatal(t, test.wantFatal, func() { + donec := make(chan struct{}) + ch := make(chan chan struct{}) + go func() { + defer close(donec) + c := <-ch + synctest.Run(func() { + test.f(c) + }) + }() synctest.Run(func() { - defer wantPanic(t, test.wantPanic) - test.f(c) + ch <- make(chan struct{}) }) - }() - synctest.Run(func() { - ch <- make(chan struct{}) + <-donec }) - <-donec }) }) } @@ -443,39 +445,40 @@ func TestTimerFromInsideBubble(t *testing.T) { for _, test := range []struct { desc string f func(tm *time.Timer) - wantPanic string + wantFatal string }{{ desc: "read channel", f: func(tm *time.Timer) { <-tm.C }, - wantPanic: "receive on synctest channel from outside bubble", + wantFatal: "receive on synctest channel from outside bubble", }, { desc: "Reset", f: func(tm *time.Timer) { tm.Reset(1 * time.Second) }, - wantPanic: "reset of synctest timer from outside bubble", + wantFatal: "reset of synctest timer from outside bubble", }, { desc: "Stop", f: func(tm *time.Timer) { tm.Stop() }, - wantPanic: "stop of synctest timer from outside bubble", + wantFatal: "stop of synctest timer from outside bubble", }} { t.Run(test.desc, func(t *testing.T) { - donec := make(chan struct{}) - ch := make(chan *time.Timer) - go func() { - defer close(donec) - defer wantPanic(t, test.wantPanic) - test.f(<-ch) - }() - synctest.Run(func() { - tm := time.NewTimer(1 * time.Second) - ch <- tm + wantFatal(t, test.wantFatal, func() { + donec := make(chan struct{}) + ch := make(chan *time.Timer) + go func() { + defer close(donec) + test.f(<-ch) + }() + synctest.Run(func() { + tm := time.NewTimer(1 * time.Second) + ch <- tm + }) + <-donec }) - <-donec }) } } diff --git a/src/runtime/chan.go b/src/runtime/chan.go index bb554ebfdb1f3a..639d29dc8337f0 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -191,7 +191,7 @@ func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool { } if c.bubble != nil && getg().bubble != c.bubble { - panic(plainError("send on synctest channel from outside bubble")) + fatal("send on synctest channel from outside bubble") } // Fast path: check for failed non-blocking operation without acquiring the lock. @@ -318,7 +318,7 @@ func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool { func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) { if c.bubble != nil && getg().bubble != c.bubble { unlockf() - panic(plainError("send on synctest channel from outside bubble")) + fatal("send on synctest channel from outside bubble") } if raceenabled { if c.dataqsiz == 0 { @@ -416,7 +416,7 @@ func closechan(c *hchan) { panic(plainError("close of nil channel")) } if c.bubble != nil && getg().bubble != c.bubble { - panic(plainError("close of synctest channel from outside bubble")) + fatal("close of synctest channel from outside bubble") } lock(&c.lock) @@ -538,7 +538,7 @@ func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) } if c.bubble != nil && getg().bubble != c.bubble { - panic(plainError("receive on synctest channel from outside bubble")) + fatal("receive on synctest channel from outside bubble") } if c.timer != nil { @@ -702,7 +702,7 @@ func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) { if c.bubble != nil && getg().bubble != c.bubble { unlockf() - panic(plainError("receive on synctest channel from outside bubble")) + fatal("receive on synctest channel from outside bubble") } if c.dataqsiz == 0 { if raceenabled { diff --git a/src/runtime/select.go b/src/runtime/select.go index ae7754b17377dd..113dc8ad19e984 100644 --- a/src/runtime/select.go +++ b/src/runtime/select.go @@ -178,7 +178,7 @@ func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, blo if cas.c.bubble != nil { if getg().bubble != cas.c.bubble { - panic(plainError("select on synctest channel from outside bubble")) + fatal("select on synctest channel from outside bubble") } } else { allSynctest = false diff --git a/src/runtime/time.go b/src/runtime/time.go index 4880dce8cddc79..e9d1f0b6c9a10f 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -415,7 +415,7 @@ func newTimer(when, period int64, f func(arg any, seq uintptr, delay int64), arg //go:linkname stopTimer time.stopTimer func stopTimer(t *timeTimer) bool { if t.isFake && getg().bubble == nil { - panic("stop of synctest timer from outside bubble") + fatal("stop of synctest timer from outside bubble") } return t.stop() } @@ -430,7 +430,7 @@ func resetTimer(t *timeTimer, when, period int64) bool { racerelease(unsafe.Pointer(&t.timer)) } if t.isFake && getg().bubble == nil { - panic("reset of synctest timer from outside bubble") + fatal("reset of synctest timer from outside bubble") } return t.reset(when, period) } From 8995e84ac64dd5d614f851a60d97ba026a9e6fb1 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 19 Aug 2025 12:10:54 +0200 Subject: [PATCH 07/59] [release-branch.go1.25] internal/poll: set the correct file offset in FD.Seek for Windows overlapped handles Windows doesn't keep the file pointer for overlapped file handles. To work around this, we keep track of the current offset ourselves and use it on every Read/Write operation. When the user calls File.Seek with whence == io.SeekCurrent, it expects that the offset we keep track of is also accounted for, else the the seek'ed value won't match the file pointer seen by the user. Fixes #75083. Change-Id: Ieca7c3779e5349292883ffc293a8474088a4dec7 Reviewed-on: https://go-review.googlesource.com/c/go/+/697275 LUCI-TryBot-Result: Go LUCI Reviewed-by: Damien Neil Reviewed-by: Dmitri Shuralyov (cherry picked from CL 697275) Reviewed-on: https://go-review.googlesource.com/c/go/+/697995 --- src/internal/poll/fd_windows.go | 6 ++++++ src/os/os_windows_test.go | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/internal/poll/fd_windows.go b/src/internal/poll/fd_windows.go index acc2ab0c6e1eaf..74188c057277ed 100644 --- a/src/internal/poll/fd_windows.go +++ b/src/internal/poll/fd_windows.go @@ -1106,6 +1106,12 @@ func (fd *FD) Seek(offset int64, whence int) (int64, error) { fd.l.Lock() defer fd.l.Unlock() + if !fd.isBlocking && whence == io.SeekCurrent { + // Windows doesn't keep the file pointer for overlapped file handles. + // We do it ourselves in case to account for any read or write + // operations that may have occurred. + offset += fd.offset + } n, err := syscall.Seek(fd.Sysfd, offset, whence) fd.setOffset(n) return n, err diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go index 515d1c135901e0..d9af25d4085c17 100644 --- a/src/os/os_windows_test.go +++ b/src/os/os_windows_test.go @@ -1845,6 +1845,44 @@ func TestFile(t *testing.T) { } } +func TestFileOverlappedSeek(t *testing.T) { + t.Parallel() + name := filepath.Join(t.TempDir(), "foo") + f := newFileOverlapped(t, name, true) + content := []byte("foo") + if _, err := f.Write(content); err != nil { + t.Fatal(err) + } + // Check that the file pointer is at the expected offset. + n, err := f.Seek(0, io.SeekCurrent) + if err != nil { + t.Fatal(err) + } + if n != int64(len(content)) { + t.Errorf("expected file pointer to be at offset %d, got %d", len(content), n) + } + // Set the file pointer to the start of the file. + if _, err := f.Seek(0, io.SeekStart); err != nil { + t.Fatal(err) + } + // Read the first byte. + var buf [1]byte + if _, err := f.Read(buf[:]); err != nil { + t.Fatal(err) + } + if !bytes.Equal(buf[:], content[:len(buf)]) { + t.Errorf("expected %q, got %q", content[:len(buf)], buf[:]) + } + // Check that the file pointer is at the expected offset. + n, err = f.Seek(0, io.SeekCurrent) + if err != nil { + t.Fatal(err) + } + if n != int64(len(buf)) { + t.Errorf("expected file pointer to be at offset %d, got %d", len(buf), n) + } +} + func TestPipe(t *testing.T) { t.Parallel() r, w, err := os.Pipe() From cdd8cf4988c7c0f2bb8eb795f74c4f803c63a70d Mon Sep 17 00:00:00 2001 From: database64128 Date: Fri, 25 Jul 2025 15:14:16 +0800 Subject: [PATCH 08/59] [release-branch.go1.25] net: fix WriteMsgUDPAddrPort addr handling on IPv4 sockets Accept IPv4-mapped IPv6 destination addresses on IPv4 UDP sockets. Fixes #74999. Change-Id: I4624b9b8f861aedcae29e51d5298d23ce1c0f2c7 Reviewed-on: https://go-review.googlesource.com/c/go/+/689976 LUCI-TryBot-Result: Go LUCI Reviewed-by: Mark Freeman Reviewed-by: Damien Neil (cherry picked from commit bdb2d50fdf40706e7d7411f33ade80edac726707) Reviewed-on: https://go-review.googlesource.com/c/go/+/695875 Reviewed-by: Cherry Mui Reviewed-by: Carlos Amedee --- src/net/ipsock_posix.go | 6 +++++- src/net/udpsock_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/net/ipsock_posix.go b/src/net/ipsock_posix.go index 2aeabd44873f22..52712f932f7530 100644 --- a/src/net/ipsock_posix.go +++ b/src/net/ipsock_posix.go @@ -237,8 +237,12 @@ func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, e func addrPortToSockaddrInet4(ap netip.AddrPort) (syscall.SockaddrInet4, error) { // ipToSockaddrInet4 has special handling here for zero length slices. // We do not, because netip has no concept of a generic zero IP address. + // + // addr is allowed to be an IPv4-mapped IPv6 address. + // As4 will unmap it to an IPv4 address. + // The error message is kept consistent with ipToSockaddrInet4. addr := ap.Addr() - if !addr.Is4() { + if !addr.Is4() && !addr.Is4In6() { return syscall.SockaddrInet4{}, &AddrError{Err: "non-IPv4 address", Addr: addr.String()} } sa := syscall.SockaddrInet4{ diff --git a/src/net/udpsock_test.go b/src/net/udpsock_test.go index 6dacc81df6e059..7ad8a585b07e33 100644 --- a/src/net/udpsock_test.go +++ b/src/net/udpsock_test.go @@ -705,3 +705,35 @@ func TestIPv6WriteMsgUDPAddrPortTargetAddrIPVersion(t *testing.T) { t.Fatal(err) } } + +// TestIPv4WriteMsgUDPAddrPortTargetAddrIPVersion verifies that +// WriteMsgUDPAddrPort accepts IPv4 and IPv4-mapped IPv6 destination addresses, +// and rejects IPv6 destination addresses on a "udp4" connection. +func TestIPv4WriteMsgUDPAddrPortTargetAddrIPVersion(t *testing.T) { + if !testableNetwork("udp4") { + t.Skipf("skipping: udp4 not available") + } + + conn, err := ListenUDP("udp4", &UDPAddr{IP: IPv4(127, 0, 0, 1)}) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + daddr4 := netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), 12345) + daddr4in6 := netip.AddrPortFrom(netip.MustParseAddr("::ffff:127.0.0.1"), 12345) + daddr6 := netip.AddrPortFrom(netip.MustParseAddr("::1"), 12345) + buf := make([]byte, 8) + + if _, _, err = conn.WriteMsgUDPAddrPort(buf, nil, daddr4); err != nil { + t.Errorf("conn.WriteMsgUDPAddrPort(buf, nil, daddr4) failed: %v", err) + } + + if _, _, err = conn.WriteMsgUDPAddrPort(buf, nil, daddr4in6); err != nil { + t.Errorf("conn.WriteMsgUDPAddrPort(buf, nil, daddr4in6) failed: %v", err) + } + + if _, _, err = conn.WriteMsgUDPAddrPort(buf, nil, daddr6); err == nil { + t.Errorf("conn.WriteMsgUDPAddrPort(buf, nil, daddr6) should have failed, but got no error") + } +} From b1959cf6f7673eaffa89bbdb00e68b30cde3aa8a Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Tue, 26 Aug 2025 16:52:39 -0400 Subject: [PATCH 09/59] [release-branch.go1.25] net/http: require exact match for CrossSiteProtection bypass patterns Fixes #75160 Updates #75054 Fixes CVE-2025-47910 Change-Id: I6a6a696440c45c450d2cd681f418b01aa0422a60 Reviewed-on: https://go-review.googlesource.com/c/go/+/699276 Reviewed-by: Roland Shoemaker Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI --- src/net/http/csrf.go | 20 ++++++++++++++------ src/net/http/csrf_test.go | 19 +++++++++++++++++-- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/net/http/csrf.go b/src/net/http/csrf.go index 5e1b686fd1ccde..d088b9b615145b 100644 --- a/src/net/http/csrf.go +++ b/src/net/http/csrf.go @@ -77,13 +77,21 @@ func (c *CrossOriginProtection) AddTrustedOrigin(origin string) error { return nil } -var noopHandler = HandlerFunc(func(w ResponseWriter, r *Request) {}) +type noopHandler struct{} + +func (noopHandler) ServeHTTP(ResponseWriter, *Request) {} + +var sentinelHandler Handler = &noopHandler{} // AddInsecureBypassPattern permits all requests that match the given pattern. -// The pattern syntax and precedence rules are the same as [ServeMux]. // -// AddInsecureBypassPattern can be called concurrently with other methods -// or request handling, and applies to future requests. +// The pattern syntax and precedence rules are the same as [ServeMux]. Only +// requests that match the pattern directly are permitted. Those that ServeMux +// would redirect to a pattern (e.g. after cleaning the path or adding a +// trailing slash) are not. +// +// AddInsecureBypassPattern can be called concurrently with other methods or +// request handling, and applies to future requests. func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) { var bypass *ServeMux @@ -99,7 +107,7 @@ func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) { } } - bypass.Handle(pattern, noopHandler) + bypass.Handle(pattern, sentinelHandler) } // SetDenyHandler sets a handler to invoke when a request is rejected. @@ -172,7 +180,7 @@ var ( // be deferred until the last moment. func (c *CrossOriginProtection) isRequestExempt(req *Request) bool { if bypass := c.bypass.Load(); bypass != nil { - if _, pattern := bypass.Handler(req); pattern != "" { + if h, _ := bypass.Handler(req); h == sentinelHandler { // The request matches a bypass pattern. return true } diff --git a/src/net/http/csrf_test.go b/src/net/http/csrf_test.go index 30986a43b9aae8..a29e3ae16dff6b 100644 --- a/src/net/http/csrf_test.go +++ b/src/net/http/csrf_test.go @@ -113,6 +113,11 @@ func TestCrossOriginProtectionPatternBypass(t *testing.T) { protection := http.NewCrossOriginProtection() protection.AddInsecureBypassPattern("/bypass/") protection.AddInsecureBypassPattern("/only/{foo}") + protection.AddInsecureBypassPattern("/no-trailing") + protection.AddInsecureBypassPattern("/yes-trailing/") + protection.AddInsecureBypassPattern("PUT /put-only/") + protection.AddInsecureBypassPattern("GET /get-only/") + protection.AddInsecureBypassPattern("POST /post-only/") handler := protection.Handler(okHandler) tests := []struct { @@ -126,13 +131,23 @@ func TestCrossOriginProtectionPatternBypass(t *testing.T) { {"non-bypass path without sec-fetch-site", "/api/", "", http.StatusForbidden}, {"non-bypass path with cross-site", "/api/", "cross-site", http.StatusForbidden}, - {"redirect to bypass path without ..", "/foo/../bypass/bar", "", http.StatusOK}, - {"redirect to bypass path with trailing slash", "/bypass", "", http.StatusOK}, + {"redirect to bypass path without ..", "/foo/../bypass/bar", "", http.StatusForbidden}, + {"redirect to bypass path with trailing slash", "/bypass", "", http.StatusForbidden}, {"redirect to non-bypass path with ..", "/foo/../api/bar", "", http.StatusForbidden}, {"redirect to non-bypass path with trailing slash", "/api", "", http.StatusForbidden}, {"wildcard bypass", "/only/123", "", http.StatusOK}, {"non-wildcard", "/only/123/foo", "", http.StatusForbidden}, + + // https://go.dev/issue/75054 + {"no trailing slash exact match", "/no-trailing", "", http.StatusOK}, + {"no trailing slash with slash", "/no-trailing/", "", http.StatusForbidden}, + {"yes trailing slash exact match", "/yes-trailing/", "", http.StatusOK}, + {"yes trailing slash without slash", "/yes-trailing", "", http.StatusForbidden}, + + {"method-specific hit", "/post-only/", "", http.StatusOK}, + {"method-specific miss (PUT)", "/put-only/", "", http.StatusForbidden}, + {"method-specific miss (GET)", "/get-only/", "", http.StatusForbidden}, } for _, tc := range tests { From 56ebf80e57db9f61981fc0636fc6419dc6f68eda Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Wed, 3 Sep 2025 10:39:10 -0700 Subject: [PATCH 10/59] [release-branch.go1.25] go1.25.1 Change-Id: I93a703d161b821cf7a78934f8711416ac6b00485 Reviewed-on: https://go-review.googlesource.com/c/go/+/700736 Auto-Submit: Gopher Robot TryBot-Bypass: Gopher Robot Reviewed-by: Michael Pratt Reviewed-by: Cherry Mui --- VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 329f88ef126ffd..cf1f2f4efa54b9 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -go1.25.0 -time 2025-08-08T19:33:32Z +go1.25.1 +time 2025-08-27T15:49:40Z From 879e3cb5f787ed56bee2f5f4d92fd013a9b47b21 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Tue, 26 Aug 2025 13:26:57 -0700 Subject: [PATCH 11/59] [release-branch.go1.25] runtime: lock mheap_.speciallock when allocating synctest specials Avoid racing use of mheap_.specialBubbleAlloc. For #75134 Fixes #75347 Change-Id: I0c9140c18d2bca1e1c3387cd81230f0e8c9ac23e Reviewed-on: https://go-review.googlesource.com/c/go/+/699255 Reviewed-by: Michael Knyszek LUCI-TryBot-Result: Go LUCI (cherry picked from commit 5dcedd65504cc9cadc9a5ea8bc3af51a26eec704) Reviewed-on: https://go-review.googlesource.com/c/go/+/701797 Reviewed-by: Damien Neil Reviewed-by: Mark Freeman Auto-Submit: Michael Knyszek --- src/internal/synctest/synctest_test.go | 22 ++++++++++++++++++++++ src/runtime/synctest.go | 2 ++ 2 files changed, 24 insertions(+) diff --git a/src/internal/synctest/synctest_test.go b/src/internal/synctest/synctest_test.go index 307eee62e2b9aa..73a0a1c453e3d5 100644 --- a/src/internal/synctest/synctest_test.go +++ b/src/internal/synctest/synctest_test.go @@ -779,6 +779,28 @@ func TestWaitGroupHeapAllocated(t *testing.T) { }) } +// Issue #75134: Many racing bubble associations. +func TestWaitGroupManyBubbles(t *testing.T) { + var wg sync.WaitGroup + for range 100 { + wg.Go(func() { + synctest.Run(func() { + cancelc := make(chan struct{}) + var wg2 sync.WaitGroup + for range 100 { + wg2.Go(func() { + <-cancelc + }) + } + synctest.Wait() + close(cancelc) + wg2.Wait() + }) + }) + } + wg.Wait() +} + func TestHappensBefore(t *testing.T) { // Use two parallel goroutines accessing different vars to ensure that // we correctly account for multiple goroutines in the bubble. diff --git a/src/runtime/synctest.go b/src/runtime/synctest.go index 16af1209b4a5f7..529f69fd9309b9 100644 --- a/src/runtime/synctest.go +++ b/src/runtime/synctest.go @@ -410,7 +410,9 @@ func getOrSetBubbleSpecial(p unsafe.Pointer, bubbleid uint64, add bool) (assoc i } else if add { // p is not associated with a bubble, // and we've been asked to add an association. + lock(&mheap_.speciallock) s := (*specialBubble)(mheap_.specialBubbleAlloc.alloc()) + unlock(&mheap_.speciallock) s.bubbleid = bubbleid s.special.kind = _KindSpecialBubble s.special.offset = offset From a86792b16938bd5f20d56e815556bbc135371fb5 Mon Sep 17 00:00:00 2001 From: Richard Miller Date: Thu, 14 Aug 2025 16:25:56 +0100 Subject: [PATCH 12/59] [release-branch.go1.25] net: skip TestIPv4WriteMsgUDPAddrPort on plan9 This test uses method (*UDPConn).WriteMsgUDPAddrPort, which is not supported on Plan 9. The test needs to be skipped, like for example TestAllocs which is already skipped for the same reason. For #75017 Fixes #75357 Change-Id: Iaa0e6ecdba0938736d8f675fcac43c46db34cb5d Reviewed-on: https://go-review.googlesource.com/c/go/+/696095 Auto-Submit: Damien Neil Reviewed-by: Damien Neil Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI (cherry picked from commit cb814bd5bc3f0575e8d0e26370c05456770cb3da) Reviewed-on: https://go-review.googlesource.com/c/go/+/704280 Reviewed-by: Mark Freeman Auto-Submit: Michael Knyszek --- src/net/udpsock_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/net/udpsock_test.go b/src/net/udpsock_test.go index 7ad8a585b07e33..3c8da43bdd9b61 100644 --- a/src/net/udpsock_test.go +++ b/src/net/udpsock_test.go @@ -710,6 +710,11 @@ func TestIPv6WriteMsgUDPAddrPortTargetAddrIPVersion(t *testing.T) { // WriteMsgUDPAddrPort accepts IPv4 and IPv4-mapped IPv6 destination addresses, // and rejects IPv6 destination addresses on a "udp4" connection. func TestIPv4WriteMsgUDPAddrPortTargetAddrIPVersion(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + } + if !testableNetwork("udp4") { t.Skipf("skipping: udp4 not available") } From be6113216595d1605ebb8fe09353e2d40aeb4b15 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 18 Aug 2025 15:49:50 +0200 Subject: [PATCH 13/59] [release-branch.go1.25] cmd/compile: export to DWARF types only referenced through interfaces Delve and viewcore use DWARF type DIEs to display and explore the runtime value of interface variables. This has always been slightly problematic since the runtime type of an interface variable might only be reachable through interfaces and thus be missing from debug_info (see issue #46670). Prior to commit f4de2ecf this was not a severe problem since a struct literal caused the allocation of a struct into an autotemp variable, which was then used by dwarfgen to make sure that the DIE for that type would be generated. After f4de2ecf such autotemps are no longer being generated and go1.25.0 ends up having many more instances of interfaces with unreadable runtime type (https://github.com/go-delve/delve/issues/4080). This commit fixes this problem by scanning the relocation of the function symbol and adding to the function's DIE symbol references to all types used by the function to create interfaces. Fixes go-delve/delve#4080 Updates #46670 Fixes #75255 Change-Id: I3e9db1c0d1662905373239816a72604ac533b09e Reviewed-on: https://go-review.googlesource.com/c/go/+/696955 Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Auto-Submit: Michael Pratt Reviewed-by: Keith Randall Reviewed-by: Than McIntosh Reviewed-by: Florian Lehner (cherry picked from commit 80038586ed2814a03dcb95cd6f130766f8d803c3) Reviewed-on: https://go-review.googlesource.com/c/go/+/704335 Reviewed-by: Alessandro Arzilli Auto-Submit: Michael Knyszek --- src/cmd/compile/internal/dwarfgen/dwarf.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go index 7d75c0c5ce38d7..4ded846ae4e657 100644 --- a/src/cmd/compile/internal/dwarfgen/dwarf.go +++ b/src/cmd/compile/internal/dwarfgen/dwarf.go @@ -128,14 +128,29 @@ func Info(ctxt *obj.Link, fnsym *obj.LSym, infosym *obj.LSym, curfn obj.Func) (s // already referenced by a dwarf var, attach an R_USETYPE relocation to // the function symbol to insure that the type included in DWARF // processing during linking. + // Do the same with R_USEIFACE relocations from the function symbol for the + // same reason. + // All these R_USETYPE relocations are only looked at if the function + // survives deadcode elimination in the linker. typesyms := []*obj.LSym{} for t := range fnsym.Func().Autot { typesyms = append(typesyms, t) } + for i := range fnsym.R { + if fnsym.R[i].Type == objabi.R_USEIFACE && !strings.HasPrefix(fnsym.R[i].Sym.Name, "go:itab.") { + // Types referenced through itab will be referenced from somewhere else + typesyms = append(typesyms, fnsym.R[i].Sym) + } + } slices.SortFunc(typesyms, func(a, b *obj.LSym) int { return strings.Compare(a.Name, b.Name) }) + var lastsym *obj.LSym for _, sym := range typesyms { + if sym == lastsym { + continue + } + lastsym = sym infosym.AddRel(ctxt, obj.Reloc{Type: objabi.R_USETYPE, Sym: sym}) } fnsym.Func().Autot = nil From 7d570090a9705a74127304cbe0d900fc1b813302 Mon Sep 17 00:00:00 2001 From: database64128 Date: Fri, 22 Aug 2025 01:03:42 +0800 Subject: [PATCH 14/59] [release-branch.go1.25] os: fix Root.MkdirAll to handle race of directory creation No tests were added, because in order to reproduce, the directory would have to be created precisely between the rootOpenDir and mkdirat calls, which is impossible to do in a test. Fixes #75116 Change-Id: I6f86a5b33c87452c35728318eaf2169a7534ef37 Reviewed-on: https://go-review.googlesource.com/c/go/+/698215 LUCI-TryBot-Result: Go LUCI Reviewed-by: Sean Liao Reviewed-by: Damien Neil Reviewed-by: Carlos Amedee Auto-Submit: Sean Liao (cherry picked from commit a076f497577605e4cf0e20c147711e03dee7b2c3) Reviewed-on: https://go-review.googlesource.com/c/go/+/700655 Reviewed-by: Mark Freeman Reviewed-by: Michael Knyszek --- src/os/root_openat.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/os/root_openat.go b/src/os/root_openat.go index e433bd5093fdb0..83bde5ef14160d 100644 --- a/src/os/root_openat.go +++ b/src/os/root_openat.go @@ -131,7 +131,9 @@ func rootMkdirAll(r *Root, fullname string, perm FileMode) error { if try > 0 || !IsNotExist(err) { return 0, &PathError{Op: "openat", Err: err} } - if err := mkdirat(parent, name, perm); err != nil { + // Try again on EEXIST, because the directory may have been created + // by another process or thread between the rootOpenDir and mkdirat calls. + if err := mkdirat(parent, name, perm); err != nil && err != syscall.EEXIST { return 0, &PathError{Op: "mkdirat", Err: err} } } From f75bcffa4a739811a10f5f08096aac93e148971e Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Fri, 22 Aug 2025 10:47:01 -0700 Subject: [PATCH 15/59] [release-branch.go1.25] os: set full name for Roots created with Root.OpenRoot Set the Name for a Root created within a Root to be the concatenation of the parent's path and the name used to open the child. This matches the behavior for files opened within a Root with Root.Open. For #73868 Fixes #75139 Change-Id: Idf4021602ac25556721b7ef6924dec652c7bf4db Reviewed-on: https://go-review.googlesource.com/c/go/+/698376 Reviewed-by: Alan Donovan LUCI-TryBot-Result: Go LUCI (cherry picked from commit ed7f804775725149088a71108efd0b20ef9f206f) Reviewed-on: https://go-review.googlesource.com/c/go/+/704277 Auto-Submit: Michael Knyszek Reviewed-by: Damien Neil --- src/os/root_test.go | 33 +++++++++++++++++++++++++++++++++ src/os/root_unix.go | 2 +- src/os/root_windows.go | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/os/root_test.go b/src/os/root_test.go index effcdeab433e78..f9fbd11575e6a0 100644 --- a/src/os/root_test.go +++ b/src/os/root_test.go @@ -1919,3 +1919,36 @@ func TestRootWriteReadFile(t *testing.T) { t.Fatalf("root.ReadFile(%q) = %q, %v; want %q, nil", name, got, err, want) } } + +func TestRootName(t *testing.T) { + dir := t.TempDir() + root, err := os.OpenRoot(dir) + if err != nil { + t.Fatal(err) + } + defer root.Close() + if got, want := root.Name(), dir; got != want { + t.Errorf("root.Name() = %q, want %q", got, want) + } + + f, err := root.Create("file") + if err != nil { + t.Fatal(err) + } + defer f.Close() + if got, want := f.Name(), filepath.Join(dir, "file"); got != want { + t.Errorf(`root.Create("file").Name() = %q, want %q`, got, want) + } + + if err := root.Mkdir("dir", 0o777); err != nil { + t.Fatal(err) + } + subroot, err := root.OpenRoot("dir") + if err != nil { + t.Fatal(err) + } + defer subroot.Close() + if got, want := subroot.Name(), filepath.Join(dir, "dir"); got != want { + t.Errorf(`root.OpenRoot("dir").Name() = %q, want %q`, got, want) + } +} diff --git a/src/os/root_unix.go b/src/os/root_unix.go index 4d6fc19a080434..c891e81b793bb5 100644 --- a/src/os/root_unix.go +++ b/src/os/root_unix.go @@ -75,7 +75,7 @@ func openRootInRoot(r *Root, name string) (*Root, error) { if err != nil { return nil, &PathError{Op: "openat", Path: name, Err: err} } - return newRoot(fd, name) + return newRoot(fd, joinPath(r.Name(), name)) } // rootOpenFileNolog is Root.OpenFile. diff --git a/src/os/root_windows.go b/src/os/root_windows.go index 523ee48d13f8c0..2ec09cf2d3f48d 100644 --- a/src/os/root_windows.go +++ b/src/os/root_windows.go @@ -123,7 +123,7 @@ func openRootInRoot(r *Root, name string) (*Root, error) { if err != nil { return nil, &PathError{Op: "openat", Path: name, Err: err} } - return newRoot(fd, name) + return newRoot(fd, joinPath(r.Name(), name)) } // rootOpenFileNolog is Root.OpenFile. From 57bd28ab7f287167e3d8915815975d72bab3687f Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sun, 7 Sep 2025 16:44:43 +0200 Subject: [PATCH 16/59] [release-branch.go1.25] crypto/internal/fips140/ecdsa: make TestingOnlyNewDRBG generic We are re-sealing the .zip file anyway for another reason, might as well take the opportunity to remove the fips140.Hash type indirection. Updates #75524 Change-Id: I6a6a6964fdb312cc2c64e327f845c398c0f6279b Reviewed-on: https://go-review.googlesource.com/c/go/+/706716 TryBot-Bypass: Filippo Valsorda Reviewed-by: Roland Shoemaker Reviewed-by: Junyang Shao --- src/crypto/internal/fips140/ecdsa/hmacdrbg.go | 2 +- src/crypto/internal/fips140/fips140.go | 7 ------- src/crypto/internal/fips140test/acvp_test.go | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/crypto/internal/fips140/ecdsa/hmacdrbg.go b/src/crypto/internal/fips140/ecdsa/hmacdrbg.go index fa82ce39f90c81..698c23bcda5161 100644 --- a/src/crypto/internal/fips140/ecdsa/hmacdrbg.go +++ b/src/crypto/internal/fips140/ecdsa/hmacdrbg.go @@ -122,7 +122,7 @@ func newDRBG[H hash.Hash](hash func() H, entropy, nonce []byte, s personalizatio // // This should only be used for ACVP testing. hmacDRBG is not intended to be // used directly. -func TestingOnlyNewDRBG(hash func() hash.Hash, entropy, nonce []byte, s []byte) *hmacDRBG { +func TestingOnlyNewDRBG[H hash.Hash](hash func() H, entropy, nonce []byte, s []byte) *hmacDRBG { return newDRBG(hash, entropy, nonce, plainPersonalizationString(s)) } diff --git a/src/crypto/internal/fips140/fips140.go b/src/crypto/internal/fips140/fips140.go index 050967f4808ad6..e05ad663749b1c 100644 --- a/src/crypto/internal/fips140/fips140.go +++ b/src/crypto/internal/fips140/fips140.go @@ -7,7 +7,6 @@ package fips140 import ( "crypto/internal/fips140deps/godebug" "errors" - "hash" "runtime" ) @@ -70,9 +69,3 @@ func Version() string { // moved to a different file. return "latest" //mkzip:version } - -// Hash is a legacy compatibility alias for hash.Hash. -// -// It's only here because [crypto/internal/fips140/ecdsa.TestingOnlyNewDRBG] -// takes a "func() fips140.Hash" in v1.0.0, instead of being generic. -type Hash = hash.Hash diff --git a/src/crypto/internal/fips140test/acvp_test.go b/src/crypto/internal/fips140test/acvp_test.go index 5871bde8be4b2b..47a42cce1bcc22 100644 --- a/src/crypto/internal/fips140test/acvp_test.go +++ b/src/crypto/internal/fips140test/acvp_test.go @@ -1624,7 +1624,7 @@ func cmdHmacDrbgAft(h func() hash.Hash) command { // * Uninstantiate // See Table 7 in draft-vassilev-acvp-drbg out := make([]byte, outLen) - drbg := ecdsa.TestingOnlyNewDRBG(func() fips140.Hash { return h() }, entropy, nonce, personalization) + drbg := ecdsa.TestingOnlyNewDRBG(h, entropy, nonce, personalization) drbg.Generate(out) drbg.Generate(out) From bec452a3a29bfe8fbcb5461327c4ceb32862a60a Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Wed, 10 Sep 2025 03:19:49 -0700 Subject: [PATCH 17/59] [release-branch.go1.25] crypto/internal/fips140: update frozen module version to "v1.0.0" We are re-sealing the .zip file anyway for another reason, might as well take the opportunity to fix the "v1.0" mistake. Note that the actual returned version change will happen when re-sealing the .zip, as the latest mkzip.go will inject "v1.0.0" instead of "v1.0". This reapplies CL 701518, reverted in CL 702255. Updates #75524 Change-Id: Ib5b3721bda35c32dd48293b3d1193c12661662dd Reviewed-on: https://go-review.googlesource.com/c/go/+/706717 Reviewed-by: Roland Shoemaker TryBot-Bypass: Filippo Valsorda Reviewed-by: Junyang Shao --- src/crypto/internal/cryptotest/hash.go | 2 +- src/crypto/internal/fips140/fips140.go | 2 +- src/crypto/internal/fips140test/fips_test.go | 5 ----- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/crypto/internal/cryptotest/hash.go b/src/crypto/internal/cryptotest/hash.go index f00e9c80d3c86a..37fd96a2d9d0b9 100644 --- a/src/crypto/internal/cryptotest/hash.go +++ b/src/crypto/internal/cryptotest/hash.go @@ -20,7 +20,7 @@ type MakeHash func() hash.Hash // TestHash performs a set of tests on hash.Hash implementations, checking the // documented requirements of Write, Sum, Reset, Size, and BlockSize. func TestHash(t *testing.T, mh MakeHash) { - if boring.Enabled || fips140.Version() == "v1.0" { + if boring.Enabled || fips140.Version() == "v1.0.0" { testhash.TestHashWithoutClone(t, testhash.MakeHash(mh)) return } diff --git a/src/crypto/internal/fips140/fips140.go b/src/crypto/internal/fips140/fips140.go index e05ad663749b1c..e48706fbd50bb9 100644 --- a/src/crypto/internal/fips140/fips140.go +++ b/src/crypto/internal/fips140/fips140.go @@ -62,7 +62,7 @@ func Name() string { return "Go Cryptographic Module" } -// Version returns the formal version (such as "v1.0") if building against a +// Version returns the formal version (such as "v1.0.0") if building against a // frozen module with GOFIPS140. Otherwise, it returns "latest". func Version() string { // This return value is replaced by mkzip.go, it must not be changed or diff --git a/src/crypto/internal/fips140test/fips_test.go b/src/crypto/internal/fips140test/fips_test.go index 08d60933ef38b8..6220e536f4aa6e 100644 --- a/src/crypto/internal/fips140test/fips_test.go +++ b/src/crypto/internal/fips140test/fips_test.go @@ -74,11 +74,6 @@ func TestVersion(t *testing.T) { continue } exp := setting.Value - if exp == "v1.0.0" { - // Unfortunately we enshrined the version of the first module as - // v1.0 before deciding to go for full versions. - exp = "v1.0" - } if v := fips140.Version(); v != exp { t.Errorf("Version is %q, expected %q", v, exp) } From 90de3b3399bbd535f7656506bf08c867b896c1e2 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Thu, 4 Sep 2025 22:19:18 -0400 Subject: [PATCH 18/59] [release-branch.go1.25] crypto/internal/fips140: remove key import PCTs, make keygen PCTs fatal CMVP clarified with the September 2nd changes to IG 10.3.A that PCTs don't need to run on imported keys. However, PCT failure must enter the error state (which for us is fatal). Thankfully, now that PCTs only run on key generation, we can be assured they will never fail. This change should only affect FIPS 140-3 mode. While at it, make the CAST/PCT testing more robust, checking TestConditional is terminated by a fatal error (and not by t.Fatal). Updates #75524 Updates #74947 Updates #69536 Change-Id: I6a6a696439e1560c10f3cce2cb208fd40c5bc641 Reviewed-on: https://go-review.googlesource.com/c/go/+/706718 TryBot-Bypass: Filippo Valsorda Reviewed-by: Roland Shoemaker Reviewed-by: Junyang Shao --- src/crypto/internal/fips140/cast.go | 17 +++- src/crypto/internal/fips140/ecdh/ecdh.go | 43 +++++---- src/crypto/internal/fips140/ecdsa/cast.go | 4 +- src/crypto/internal/fips140/ecdsa/ecdsa.go | 10 +- src/crypto/internal/fips140/ed25519/cast.go | 4 +- .../internal/fips140/ed25519/ed25519.go | 15 +-- .../internal/fips140/mlkem/mlkem1024.go | 9 +- src/crypto/internal/fips140/mlkem/mlkem768.go | 9 +- src/crypto/internal/fips140/rsa/keygen.go | 23 ++++- src/crypto/internal/fips140/rsa/rsa.go | 20 ---- src/crypto/internal/fips140test/cast_test.go | 92 ++++++++++--------- 11 files changed, 111 insertions(+), 135 deletions(-) diff --git a/src/crypto/internal/fips140/cast.go b/src/crypto/internal/fips140/cast.go index 66e21d8a90dbc9..3968dcadd4da8f 100644 --- a/src/crypto/internal/fips140/cast.go +++ b/src/crypto/internal/fips140/cast.go @@ -56,9 +56,10 @@ func CAST(name string, f func() error) { } // PCT runs the named Pairwise Consistency Test (if operated in FIPS mode) and -// returns any errors. If an error is returned, the key must not be used. +// aborts the program (stopping the module input/output and entering the "error +// state") if the test fails. // -// PCTs are mandatory for every key pair that is generated/imported, including +// PCTs are mandatory for every generated (but not imported) key pair, including // ephemeral keys (which effectively doubles the cost of key establishment). See // Implementation Guidance 10.3.A Additional Comment 1. // @@ -66,17 +67,23 @@ func CAST(name string, f func() error) { // // If a package p calls PCT during key generation, an invocation of that // function should be added to fipstest.TestConditionals. -func PCT(name string, f func() error) error { +func PCT(name string, f func() error) { if strings.ContainsAny(name, ",#=:") { panic("fips: invalid self-test name: " + name) } if !Enabled { - return nil + return } err := f() if name == failfipscast { err = errors.New("simulated PCT failure") } - return err + if err != nil { + fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error()) + panic("unreachable") + } + if debug { + println("FIPS 140-3 PCT passed:", name) + } } diff --git a/src/crypto/internal/fips140/ecdh/ecdh.go b/src/crypto/internal/fips140/ecdh/ecdh.go index bf71c75a92c6eb..967032aab28fc0 100644 --- a/src/crypto/internal/fips140/ecdh/ecdh.go +++ b/src/crypto/internal/fips140/ecdh/ecdh.go @@ -161,6 +161,27 @@ func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) { if err != nil { continue } + + // A "Pairwise Consistency Test" makes no sense if we just generated the + // public key from an ephemeral private key. Moreover, there is no way to + // check it aside from redoing the exact same computation again. SP 800-56A + // Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it. + // However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a + // PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional + // Comment 1 goes out of its way to say that "the PCT shall be performed + // consistent [...], even if the underlying standard does not require a + // PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode. + fips140.PCT("ECDH PCT", func() error { + p1, err := c.newPoint().ScalarBaseMult(privateKey.d) + if err != nil { + return err + } + if !bytes.Equal(p1.Bytes(), privateKey.pub.q) { + return errors.New("crypto/ecdh: public key does not match private key") + } + return nil + }) + return privateKey, nil } } @@ -188,28 +209,6 @@ func NewPrivateKey[P Point[P]](c *Curve[P], key []byte) (*PrivateKey, error) { panic("crypto/ecdh: internal error: public key is the identity element") } - // A "Pairwise Consistency Test" makes no sense if we just generated the - // public key from an ephemeral private key. Moreover, there is no way to - // check it aside from redoing the exact same computation again. SP 800-56A - // Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it. - // However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a - // PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional - // Comment 1 goes out of its way to say that "the PCT shall be performed - // consistent [...], even if the underlying standard does not require a - // PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode. - if err := fips140.PCT("ECDH PCT", func() error { - p1, err := c.newPoint().ScalarBaseMult(key) - if err != nil { - return err - } - if !bytes.Equal(p1.Bytes(), publicKey) { - return errors.New("crypto/ecdh: public key does not match private key") - } - return nil - }); err != nil { - panic(err) - } - k := &PrivateKey{d: bytes.Clone(key), pub: PublicKey{curve: c.curve, q: publicKey}} return k, nil } diff --git a/src/crypto/internal/fips140/ecdsa/cast.go b/src/crypto/internal/fips140/ecdsa/cast.go index 219b7211e74e92..6bc9fd1f46d2d0 100644 --- a/src/crypto/internal/fips140/ecdsa/cast.go +++ b/src/crypto/internal/fips140/ecdsa/cast.go @@ -51,8 +51,8 @@ func testHash() []byte { } } -func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error { - return fips140.PCT("ECDSA PCT", func() error { +func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) { + fips140.PCT("ECDSA PCT", func() error { hash := testHash() drbg := newDRBG(sha512.New, k.d, bits2octets(P256(), hash), nil) sig, err := sign(c, k, drbg, hash) diff --git a/src/crypto/internal/fips140/ecdsa/ecdsa.go b/src/crypto/internal/fips140/ecdsa/ecdsa.go index 47c1b2442144e4..81179de4f4e001 100644 --- a/src/crypto/internal/fips140/ecdsa/ecdsa.go +++ b/src/crypto/internal/fips140/ecdsa/ecdsa.go @@ -167,11 +167,6 @@ func NewPrivateKey[P Point[P]](c *Curve[P], D, Q []byte) (*PrivateKey, error) { return nil, err } priv := &PrivateKey{pub: *pub, d: d.Bytes(c.N)} - if err := fipsPCT(c, priv); err != nil { - // This can happen if the application went out of its way to make an - // ecdsa.PrivateKey with a mismatching PublicKey. - return nil, err - } return priv, nil } @@ -204,10 +199,7 @@ func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) { }, d: k.Bytes(c.N), } - if err := fipsPCT(c, priv); err != nil { - // This clearly can't happen, but FIPS 140-3 mandates that we check it. - panic(err) - } + fipsPCT(c, priv) return priv, nil } diff --git a/src/crypto/internal/fips140/ed25519/cast.go b/src/crypto/internal/fips140/ed25519/cast.go index a680c2514b816e..2a3426bd42f50e 100644 --- a/src/crypto/internal/fips140/ed25519/cast.go +++ b/src/crypto/internal/fips140/ed25519/cast.go @@ -12,8 +12,8 @@ import ( "sync" ) -func fipsPCT(k *PrivateKey) error { - return fips140.PCT("Ed25519 sign and verify PCT", func() error { +func fipsPCT(k *PrivateKey) { + fips140.PCT("Ed25519 sign and verify PCT", func() error { return pairwiseTest(k) }) } diff --git a/src/crypto/internal/fips140/ed25519/ed25519.go b/src/crypto/internal/fips140/ed25519/ed25519.go index bbdc5b4a8ba2f9..8beda341d941ad 100644 --- a/src/crypto/internal/fips140/ed25519/ed25519.go +++ b/src/crypto/internal/fips140/ed25519/ed25519.go @@ -69,10 +69,7 @@ func generateKey(priv *PrivateKey) (*PrivateKey, error) { fips140.RecordApproved() drbg.Read(priv.seed[:]) precomputePrivateKey(priv) - if err := fipsPCT(priv); err != nil { - // This clearly can't happen, but FIPS 140-3 requires that we check. - panic(err) - } + fipsPCT(priv) return priv, nil } @@ -88,10 +85,6 @@ func newPrivateKeyFromSeed(priv *PrivateKey, seed []byte) (*PrivateKey, error) { } copy(priv.seed[:], seed) precomputePrivateKey(priv) - if err := fipsPCT(priv); err != nil { - // This clearly can't happen, but FIPS 140-3 requires that we check. - panic(err) - } return priv, nil } @@ -137,12 +130,6 @@ func newPrivateKey(priv *PrivateKey, privBytes []byte) (*PrivateKey, error) { copy(priv.prefix[:], h[32:]) - if err := fipsPCT(priv); err != nil { - // This can happen if the application messed with the private key - // encoding, and the public key doesn't match the seed anymore. - return nil, err - } - return priv, nil } diff --git a/src/crypto/internal/fips140/mlkem/mlkem1024.go b/src/crypto/internal/fips140/mlkem/mlkem1024.go index 034bf3b5d6682d..1419cf20fa9c67 100644 --- a/src/crypto/internal/fips140/mlkem/mlkem1024.go +++ b/src/crypto/internal/fips140/mlkem/mlkem1024.go @@ -118,10 +118,7 @@ func generateKey1024(dk *DecapsulationKey1024) (*DecapsulationKey1024, error) { var z [32]byte drbg.Read(z[:]) kemKeyGen1024(dk, &d, &z) - if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil { - // This clearly can't happen, but FIPS 140-3 requires us to check. - panic(err) - } + fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }) fips140.RecordApproved() return dk, nil } @@ -149,10 +146,6 @@ func newKeyFromSeed1024(dk *DecapsulationKey1024, seed []byte) (*DecapsulationKe d := (*[32]byte)(seed[:32]) z := (*[32]byte)(seed[32:]) kemKeyGen1024(dk, d, z) - if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil { - // This clearly can't happen, but FIPS 140-3 requires us to check. - panic(err) - } fips140.RecordApproved() return dk, nil } diff --git a/src/crypto/internal/fips140/mlkem/mlkem768.go b/src/crypto/internal/fips140/mlkem/mlkem768.go index 77043830d4d962..298660e4e977dd 100644 --- a/src/crypto/internal/fips140/mlkem/mlkem768.go +++ b/src/crypto/internal/fips140/mlkem/mlkem768.go @@ -177,10 +177,7 @@ func generateKey(dk *DecapsulationKey768) (*DecapsulationKey768, error) { var z [32]byte drbg.Read(z[:]) kemKeyGen(dk, &d, &z) - if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil { - // This clearly can't happen, but FIPS 140-3 requires us to check. - panic(err) - } + fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }) fips140.RecordApproved() return dk, nil } @@ -208,10 +205,6 @@ func newKeyFromSeed(dk *DecapsulationKey768, seed []byte) (*DecapsulationKey768, d := (*[32]byte)(seed[:32]) z := (*[32]byte)(seed[32:]) kemKeyGen(dk, d, z) - if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil { - // This clearly can't happen, but FIPS 140-3 requires us to check. - panic(err) - } fips140.RecordApproved() return dk, nil } diff --git a/src/crypto/internal/fips140/rsa/keygen.go b/src/crypto/internal/fips140/rsa/keygen.go index 7c0272239ff3cf..00b325d24b2211 100644 --- a/src/crypto/internal/fips140/rsa/keygen.go +++ b/src/crypto/internal/fips140/rsa/keygen.go @@ -105,7 +105,28 @@ func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) { // negligible chance of failure we can defer the check to the end of key // generation and return an error if it fails. See [checkPrivateKey]. - return newPrivateKey(N, 65537, d, P, Q) + k, err := newPrivateKey(N, 65537, d, P, Q) + if err != nil { + return nil, err + } + + if k.fipsApproved { + fips140.PCT("RSA sign and verify PCT", func() error { + hash := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + } + sig, err := signPKCS1v15(k, "SHA-256", hash) + if err != nil { + return err + } + return verifyPKCS1v15(k.PublicKey(), "SHA-256", hash, sig) + }) + } + + return k, nil } } diff --git a/src/crypto/internal/fips140/rsa/rsa.go b/src/crypto/internal/fips140/rsa/rsa.go index 0bbf7010506107..764338940a3282 100644 --- a/src/crypto/internal/fips140/rsa/rsa.go +++ b/src/crypto/internal/fips140/rsa/rsa.go @@ -310,26 +310,6 @@ func checkPrivateKey(priv *PrivateKey) error { return errors.New("crypto/rsa: d too small") } - // If the key is still in scope for FIPS mode, perform a Pairwise - // Consistency Test. - if priv.fipsApproved { - if err := fips140.PCT("RSA sign and verify PCT", func() error { - hash := []byte{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - } - sig, err := signPKCS1v15(priv, "SHA-256", hash) - if err != nil { - return err - } - return verifyPKCS1v15(priv.PublicKey(), "SHA-256", hash, sig) - }); err != nil { - return err - } - } - return nil } diff --git a/src/crypto/internal/fips140test/cast_test.go b/src/crypto/internal/fips140test/cast_test.go index 1818b583d5ce2d..14ab72d1713a72 100644 --- a/src/crypto/internal/fips140test/cast_test.go +++ b/src/crypto/internal/fips140test/cast_test.go @@ -5,9 +5,9 @@ package fipstest import ( + "crypto" + "crypto/internal/fips140" "crypto/rand" - "crypto/x509" - "encoding/pem" "fmt" "internal/testenv" "io/fs" @@ -50,8 +50,6 @@ var allCASTs = []string{ "KAS-ECC-SSC P-256", "ML-KEM PCT", "ML-KEM PCT", - "ML-KEM PCT", - "ML-KEM PCT", "ML-KEM-768", "PBKDF2", "RSA sign and verify PCT", @@ -107,60 +105,65 @@ func TestAllCASTs(t *testing.T) { // TestConditionals causes the conditional CASTs and PCTs to be invoked. func TestConditionals(t *testing.T) { mlkem.GenerateKey768() - k, err := ecdh.GenerateKey(ecdh.P256(), rand.Reader) + kDH, err := ecdh.GenerateKey(ecdh.P256(), rand.Reader) if err != nil { - t.Fatal(err) + t.Error(err) + } else { + ecdh.ECDH(ecdh.P256(), kDH, kDH.PublicKey()) } - ecdh.ECDH(ecdh.P256(), k, k.PublicKey()) kDSA, err := ecdsa.GenerateKey(ecdsa.P256(), rand.Reader) if err != nil { - t.Fatal(err) + t.Error(err) + } else { + ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32)) } - ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32)) k25519, err := ed25519.GenerateKey() if err != nil { - t.Fatal(err) + t.Error(err) + } else { + ed25519.Sign(k25519, make([]byte, 32)) } - ed25519.Sign(k25519, make([]byte, 32)) - rsa.VerifyPKCS1v15(&rsa.PublicKey{}, "", nil, nil) - // Parse an RSA key to hit the PCT rather than generating one (which is slow). - block, _ := pem.Decode([]byte(strings.ReplaceAll( - `-----BEGIN RSA TESTING KEY----- -MIIEowIBAAKCAQEAsPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqso -tWxQYLEYzNEx5ZSHTGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE -89FU1nZQF15oVLOpUgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNU -l86BU02vlBiESxOuox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9s -B6iDjj70HFldzOQ9r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P59 -3VVJvnzOjaA1z6Cz+4+eRvcysqhrRgFlwI9TEwIDAQABAoIBAEEYiyDP29vCzx/+ -dS3LqnI5BjUuJhXUnc6AWX/PCgVAO+8A+gZRgvct7PtZb0sM6P9ZcLrweomlGezI -FrL0/6xQaa8bBr/ve/a8155OgcjFo6fZEw3Dz7ra5fbSiPmu4/b/kvrg+Br1l77J -aun6uUAs1f5B9wW+vbR7tzbT/mxaUeDiBzKpe15GwcvbJtdIVMa2YErtRjc1/5B2 -BGVXyvlJv0SIlcIEMsHgnAFOp1ZgQ08aDzvilLq8XVMOahAhP1O2A3X8hKdXPyrx -IVWE9bS9ptTo+eF6eNl+d7htpKGEZHUxinoQpWEBTv+iOoHsVunkEJ3vjLP3lyI/ -fY0NQ1ECgYEA3RBXAjgvIys2gfU3keImF8e/TprLge1I2vbWmV2j6rZCg5r/AS0u -pii5CvJ5/T5vfJPNgPBy8B/yRDs+6PJO1GmnlhOkG9JAIPkv0RBZvR0PMBtbp6nT -Y3yo1lwamBVBfY6rc0sLTzosZh2aGoLzrHNMQFMGaauORzBFpY5lU50CgYEAzPHl -u5DI6Xgep1vr8QvCUuEesCOgJg8Yh1UqVoY/SmQh6MYAv1I9bLGwrb3WW/7kqIoD -fj0aQV5buVZI2loMomtU9KY5SFIsPV+JuUpy7/+VE01ZQM5FdY8wiYCQiVZYju9X -Wz5LxMNoz+gT7pwlLCsC4N+R8aoBk404aF1gum8CgYAJ7VTq7Zj4TFV7Soa/T1eE -k9y8a+kdoYk3BASpCHJ29M5R2KEA7YV9wrBklHTz8VzSTFTbKHEQ5W5csAhoL5Fo -qoHzFFi3Qx7MHESQb9qHyolHEMNx6QdsHUn7rlEnaTTyrXh3ifQtD6C0yTmFXUIS -CW9wKApOrnyKJ9nI0HcuZQKBgQCMtoV6e9VGX4AEfpuHvAAnMYQFgeBiYTkBKltQ -XwozhH63uMMomUmtSG87Sz1TmrXadjAhy8gsG6I0pWaN7QgBuFnzQ/HOkwTm+qKw -AsrZt4zeXNwsH7QXHEJCFnCmqw9QzEoZTrNtHJHpNboBuVnYcoueZEJrP8OnUG3r -UjmopwKBgAqB2KYYMUqAOvYcBnEfLDmyZv9BTVNHbR2lKkMYqv5LlvDaBxVfilE0 -2riO4p6BaAdvzXjKeRrGNEKoHNBpOSfYCOM16NjL8hIZB1CaV3WbT5oY+jp7Mzd5 -7d56RZOE+ERK2uz/7JX9VSsM/LbH9pJibd4e8mikDS9ntciqOH/3 ------END RSA TESTING KEY-----`, "TESTING KEY", "PRIVATE KEY"))) - if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err != nil { - t.Fatal(err) + kRSA, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Error(err) + } else { + rsa.SignPKCS1v15(kRSA, crypto.SHA256.String(), make([]byte, 32)) } t.Log("completed successfully") } +func TestCASTPasses(t *testing.T) { + moduleStatus(t) + testenv.MustHaveExec(t) + if err := fips140.Supported(); err != nil { + t.Skipf("test requires FIPS 140 mode: %v", err) + } + + cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestConditionals$", "-test.v") + cmd.Env = append(cmd.Env, "GODEBUG=fips140=debug") + out, err := cmd.CombinedOutput() + t.Logf("%s", out) + if err != nil || !strings.Contains(string(out), "completed successfully") { + t.Errorf("TestConditionals did not complete successfully") + } + + for _, name := range allCASTs { + t.Run(name, func(t *testing.T) { + if !strings.Contains(string(out), fmt.Sprintf("passed: %s\n", name)) { + t.Errorf("CAST/PCT %s success was not logged", name) + } else { + t.Logf("CAST/PCT succeeded: %s", name) + } + }) + } +} + func TestCASTFailures(t *testing.T) { moduleStatus(t) testenv.MustHaveExec(t) + if err := fips140.Supported(); err != nil { + t.Skipf("test requires FIPS 140 mode: %v", err) + } for _, name := range allCASTs { t.Run(name, func(t *testing.T) { @@ -169,7 +172,6 @@ func TestCASTFailures(t *testing.T) { if !testing.Verbose() { t.Parallel() } - t.Logf("CAST/PCT succeeded: %s", name) t.Logf("Testing CAST/PCT failure...") cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestConditionals$", "-test.v") cmd.Env = append(cmd.Env, fmt.Sprintf("GODEBUG=failfipscast=%s,fips140=on", name)) @@ -180,6 +182,8 @@ func TestCASTFailures(t *testing.T) { } if strings.Contains(string(out), "completed successfully") { t.Errorf("CAST/PCT %s failure did not stop the program", name) + } else if !strings.Contains(string(out), "self-test failed: "+name) { + t.Errorf("CAST/PCT %s failure did not log the expected message", name) } else { t.Logf("CAST/PCT %s failed as expected and caused the program to exit", name) } From b816c79658cb9dd9654dab20430f0e7e261aacc5 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sun, 7 Sep 2025 16:52:08 +0200 Subject: [PATCH 19/59] [release-branch.go1.25] lib/fips140: re-seal v1.0.0 Exceptionally, we decided to make a compliance-related change following CMVP's updated Implementation Guidance on September 2nd. The Security Policy will be updated to reflect the new zip hash. mkzip.go has been modified to accept versions of the form vX.Y.Z-hash, where the -hash suffix is ignored for fips140.Version() but used to name the zip file and the unpacked cache directory. The new zip is generated with go run ../../src/cmd/go/internal/fips140/mkzip.go -b c2097c7c v1.0.0-c2097c7c from c2097c7c which is the current release-branch.go1.24 head. The full diff between the zip file contents is included below. Fixes #75524 For #74947 Updates #69536 $ diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/cast.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/cast.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/cast.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/cast.go 1980-01-10 00:00:00.000000000 +0100 @@ -56,9 +56,10 @@ } // PCT runs the named Pairwise Consistency Test (if operated in FIPS mode) and -// returns any errors. If an error is returned, the key must not be used. +// aborts the program (stopping the module input/output and entering the "error +// state") if the test fails. // -// PCTs are mandatory for every key pair that is generated/imported, including +// PCTs are mandatory for every generated (but not imported) key pair, including // ephemeral keys (which effectively doubles the cost of key establishment). See // Implementation Guidance 10.3.A Additional Comment 1. // @@ -66,17 +67,23 @@ // // If a package p calls PCT during key generation, an invocation of that // function should be added to fipstest.TestConditionals. -func PCT(name string, f func() error) error { +func PCT(name string, f func() error) { if strings.ContainsAny(name, ",#=:") { panic("fips: invalid self-test name: " + name) } if !Enabled { - return nil + return } err := f() if name == failfipscast { err = errors.New("simulated PCT failure") } - return err + if err != nil { + fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error()) + panic("unreachable") + } + if debug { + println("FIPS 140-3 PCT passed:", name) + } } diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdh/ecdh.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdh/ecdh.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdh/ecdh.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdh/ecdh.go 1980-01-10 00:00:00.000000000 +0100 @@ -161,6 +161,27 @@ if err != nil { continue } + + // A "Pairwise Consistency Test" makes no sense if we just generated the + // public key from an ephemeral private key. Moreover, there is no way to + // check it aside from redoing the exact same computation again. SP 800-56A + // Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it. + // However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a + // PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional + // Comment 1 goes out of its way to say that "the PCT shall be performed + // consistent [...], even if the underlying standard does not require a + // PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode. + fips140.PCT("ECDH PCT", func() error { + p1, err := c.newPoint().ScalarBaseMult(privateKey.d) + if err != nil { + return err + } + if !bytes.Equal(p1.Bytes(), privateKey.pub.q) { + return errors.New("crypto/ecdh: public key does not match private key") + } + return nil + }) + return privateKey, nil } } @@ -188,28 +209,6 @@ panic("crypto/ecdh: internal error: public key is the identity element") } - // A "Pairwise Consistency Test" makes no sense if we just generated the - // public key from an ephemeral private key. Moreover, there is no way to - // check it aside from redoing the exact same computation again. SP 800-56A - // Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it. - // However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a - // PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional - // Comment 1 goes out of its way to say that "the PCT shall be performed - // consistent [...], even if the underlying standard does not require a - // PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode. - if err := fips140.PCT("ECDH PCT", func() error { - p1, err := c.newPoint().ScalarBaseMult(key) - if err != nil { - return err - } - if !bytes.Equal(p1.Bytes(), publicKey) { - return errors.New("crypto/ecdh: public key does not match private key") - } - return nil - }); err != nil { - panic(err) - } - k := &PrivateKey{d: bytes.Clone(key), pub: PublicKey{curve: c.curve, q: publicKey}} return k, nil } diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/cast.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/cast.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/cast.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/cast.go 1980-01-10 00:00:00.000000000 +0100 @@ -51,8 +51,8 @@ } } -func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error { - return fips140.PCT("ECDSA PCT", func() error { +func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) { + fips140.PCT("ECDSA PCT", func() error { hash := testHash() drbg := newDRBG(sha512.New, k.d, bits2octets(P256(), hash), nil) sig, err := sign(c, k, drbg, hash) diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/ecdsa.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/ecdsa.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/ecdsa.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/ecdsa.go 1980-01-10 00:00:00.000000000 +0100 @@ -166,11 +166,6 @@ return nil, err } priv := &PrivateKey{pub: *pub, d: d.Bytes(c.N)} - if err := fipsPCT(c, priv); err != nil { - // This can happen if the application went out of its way to make an - // ecdsa.PrivateKey with a mismatching PublicKey. - return nil, err - } return priv, nil } @@ -203,10 +198,7 @@ }, d: k.Bytes(c.N), } - if err := fipsPCT(c, priv); err != nil { - // This clearly can't happen, but FIPS 140-3 mandates that we check it. - panic(err) - } + fipsPCT(c, priv) return priv, nil } diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/hmacdrbg.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/hmacdrbg.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/hmacdrbg.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/hmacdrbg.go 1980-01-10 00:00:00.000000000 +0100 @@ -121,7 +121,7 @@ // // This should only be used for ACVP testing. hmacDRBG is not intended to be // used directly. -func TestingOnlyNewDRBG(hash func() fips140.Hash, entropy, nonce []byte, s []byte) *hmacDRBG { +func TestingOnlyNewDRBG[H fips140.Hash](hash func() H, entropy, nonce []byte, s []byte) *hmacDRBG { return newDRBG(hash, entropy, nonce, plainPersonalizationString(s)) } diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/cast.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/cast.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/cast.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/cast.go 1980-01-10 00:00:00.000000000 +0100 @@ -12,8 +12,8 @@ "sync" ) -func fipsPCT(k *PrivateKey) error { - return fips140.PCT("Ed25519 sign and verify PCT", func() error { +func fipsPCT(k *PrivateKey) { + fips140.PCT("Ed25519 sign and verify PCT", func() error { return pairwiseTest(k) }) } diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/ed25519.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/ed25519.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/ed25519.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/ed25519.go 1980-01-10 00:00:00.000000000 +0100 @@ -69,10 +69,7 @@ fips140.RecordApproved() drbg.Read(priv.seed[:]) precomputePrivateKey(priv) - if err := fipsPCT(priv); err != nil { - // This clearly can't happen, but FIPS 140-3 requires that we check. - panic(err) - } + fipsPCT(priv) return priv, nil } @@ -88,10 +85,6 @@ } copy(priv.seed[:], seed) precomputePrivateKey(priv) - if err := fipsPCT(priv); err != nil { - // This clearly can't happen, but FIPS 140-3 requires that we check. - panic(err) - } return priv, nil } @@ -137,12 +130,6 @@ copy(priv.prefix[:], h[32:]) - if err := fipsPCT(priv); err != nil { - // This can happen if the application messed with the private key - // encoding, and the public key doesn't match the seed anymore. - return nil, err - } - return priv, nil } diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/fips140.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/fips140.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/fips140.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/fips140.go 1980-01-10 00:00:00.000000000 +0100 @@ -62,6 +62,10 @@ return "Go Cryptographic Module" } +// Version returns the formal version (such as "v1.0.0") if building against a +// frozen module with GOFIPS140. Otherwise, it returns "latest". func Version() string { - return "v1.0" + // This return value is replaced by mkzip.go, it must not be changed or + // moved to a different file. + return "v1.0.0" } diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem1024.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem1024.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem1024.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem1024.go 1980-01-10 00:00:00.000000000 +0100 @@ -118,10 +118,7 @@ var z [32]byte drbg.Read(z[:]) kemKeyGen1024(dk, &d, &z) - if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil { - // This clearly can't happen, but FIPS 140-3 requires us to check. - panic(err) - } + fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }) fips140.RecordApproved() return dk, nil } @@ -149,10 +146,6 @@ d := (*[32]byte)(seed[:32]) z := (*[32]byte)(seed[32:]) kemKeyGen1024(dk, d, z) - if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil { - // This clearly can't happen, but FIPS 140-3 requires us to check. - panic(err) - } fips140.RecordApproved() return dk, nil } diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem768.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem768.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem768.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem768.go 1980-01-10 00:00:00.000000000 +0100 @@ -177,10 +177,7 @@ var z [32]byte drbg.Read(z[:]) kemKeyGen(dk, &d, &z) - if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil { - // This clearly can't happen, but FIPS 140-3 requires us to check. - panic(err) - } + fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }) fips140.RecordApproved() return dk, nil } @@ -208,10 +205,6 @@ d := (*[32]byte)(seed[:32]) z := (*[32]byte)(seed[32:]) kemKeyGen(dk, d, z) - if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil { - // This clearly can't happen, but FIPS 140-3 requires us to check. - panic(err) - } fips140.RecordApproved() return dk, nil } diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/keygen.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/keygen.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/keygen.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/keygen.go 1980-01-10 00:00:00.000000000 +0100 @@ -105,7 +105,28 @@ // negligible chance of failure we can defer the check to the end of key // generation and return an error if it fails. See [checkPrivateKey]. - return newPrivateKey(N, 65537, d, P, Q) + k, err := newPrivateKey(N, 65537, d, P, Q) + if err != nil { + return nil, err + } + + if k.fipsApproved { + fips140.PCT("RSA sign and verify PCT", func() error { + hash := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + } + sig, err := signPKCS1v15(k, "SHA-256", hash) + if err != nil { + return err + } + return verifyPKCS1v15(k.PublicKey(), "SHA-256", hash, sig) + }) + } + + return k, nil } } diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/rsa.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/rsa.go --- golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/rsa.go 1980-01-10 00:00:00.000000000 +0100 +++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/rsa.go 1980-01-10 00:00:00.000000000 +0100 @@ -310,26 +310,6 @@ return errors.New("crypto/rsa: d too small") } - // If the key is still in scope for FIPS mode, perform a Pairwise - // Consistency Test. - if priv.fipsApproved { - if err := fips140.PCT("RSA sign and verify PCT", func() error { - hash := []byte{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - } - sig, err := signPKCS1v15(priv, "SHA-256", hash) - if err != nil { - return err - } - return verifyPKCS1v15(priv.PublicKey(), "SHA-256", hash, sig) - }); err != nil { - return err - } - } - return nil } Change-Id: I6a6a6964b1780f19ec2b5202052de58b47d9342c Reviewed-on: https://go-review.googlesource.com/c/go/+/701520 Reviewed-by: Junyang Shao Auto-Submit: Filippo Valsorda Commit-Queue: Junyang Shao Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-on: https://go-review.googlesource.com/c/go/+/706719 --- lib/fips140/fips140.sum | 2 +- lib/fips140/inprocess.txt | 2 +- .../{v1.0.0.zip => v1.0.0-c2097c7c.zip} | Bin 650281 -> 658130 bytes lib/fips140/v1.0.0.txt | 1 + src/cmd/go/internal/fips140/mkzip.go | 8 +++++--- src/cmd/go/testdata/script/fipssnap.txt | 5 ++--- src/crypto/internal/fips140test/fips_test.go | 3 +++ src/internal/buildcfg/cfg.go | 5 +++-- 8 files changed, 16 insertions(+), 10 deletions(-) rename lib/fips140/{v1.0.0.zip => v1.0.0-c2097c7c.zip} (78%) create mode 100644 lib/fips140/v1.0.0.txt diff --git a/lib/fips140/fips140.sum b/lib/fips140/fips140.sum index 66b1e23dfe619d..703d1dc60e58ab 100644 --- a/lib/fips140/fips140.sum +++ b/lib/fips140/fips140.sum @@ -9,4 +9,4 @@ # # go test cmd/go/internal/fips140 -update # -v1.0.0.zip b50508feaeff05d22516b21e1fd210bbf5d6a1e422eaf2cfa23fe379342713b8 +v1.0.0-c2097c7c.zip daf3614e0406f67ae6323c902db3f953a1effb199142362a039e7526dfb9368b diff --git a/lib/fips140/inprocess.txt b/lib/fips140/inprocess.txt index 0ec25f7505c14b..efd3caba85ba62 100644 --- a/lib/fips140/inprocess.txt +++ b/lib/fips140/inprocess.txt @@ -1 +1 @@ -v1.0.0 +v1.0.0-c2097c7c diff --git a/lib/fips140/v1.0.0.zip b/lib/fips140/v1.0.0-c2097c7c.zip similarity index 78% rename from lib/fips140/v1.0.0.zip rename to lib/fips140/v1.0.0-c2097c7c.zip index bd9d3c19d05b9075a2587806d00224c3f8859ba1..aabf762d0fecb29fcf3e42fbf8acefc994cf8358 100644 GIT binary patch delta 60205 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

g^bw;!&4w3UNR zP@WY$s2C-oAuqb3ep2{s^W`I6K6@w%{I#?_Bs~722L`1=U@S3!!DaD@FWD-h?L%0U zB{Xs5abN^HO!-nMONn5@BrTJ|6)7N_7=9%XREbVqD&HA4LU&|fj#8E#P6sq68(<|W zZ5N+)AD%iSbOhlxrOLT;qTF2*POM0s6t6xp?Kl@<8nXgJFP6bTl0zNX3OyiQ5s}IW zS{}|Z8R2(d3Ez5o6T~1Y?Nl)As`tynmYHLTdZqr5uA07W8L?~V2xaR2hZR(nVX~eV zErUXv(N(nDJGHifYYXY_)9zo!A%N=-TzIHU&u!6#pRuu1RNiLjMPtIU$4X*u{5x6AzO92~PVqI>?2@hI~J%zaCyKf+rwJ1C^k+m0(D}!O#b;ri6SM!Us<q_u;oJ`@K`#6LeN&YDB%HM8Q*sZx`o_&Ur5mqX>}+@&k&YXnMUOaVBV->aJv%1H&HTz>6}AGpY?Od8!6qbVVv zoQ&0#3{IWS%d$v>`EMsQER+dTcZQ2Q{A0U~=T+irk~SAhuDiGE{!7U1^{$7Kxlm44 zC<7z=zSQWmQyt6*5H|E|oy5WkU}UH8&35|pK-m78b-C!*H5Zol9(N(@{y1XdDmj_v zAcN{JpSC|yBmuTYJ`Tzl^W;r#>`wOn!lWJE(YI}4PEhyhwxYwv@GCC3l{T{v9CH)< z?6O~G_uN_26u(N8kmEdNO&}}A9^gZ#$rSB~pN4l{|C2WnFo2kLbU0y61S^P?zK>{g zV4ulk&zJ817vEjevExKT$#%)?*SI2t3H#Ij!`XuIjeNm6sv=9C#XGj2s~u*Oq9q*! zd;Ea1);7SSFg~#%tcm3};XVhMmRxz=dwT(?O!D{K$YZtePNQup{au3mEA)Emp*kT70-C$N0DRymVIye z_%Xdm(OAw_4m-qJu?K30UhiKqS+GScrjgnMr){QhJgX{!-tvI03@K1D3Y0ZPNkK;&zm6uzL$*7 z;V0%)&?WCD#Dp8D4^r4(9@cwe#2FA(&8>8)kaV_J3TUz2^yxSdZ_61h46I^McOu2= zSILD@XDdg(HLvfxUq452-FrDk?(RMv9qm9XykGD3@05rRNIB3-W-Q|^fParr&lkzI zW;KNZ2Y7Cs!;$8!UDl`{P67fTJ3jjU1$NVKI+JZ1fV(fi2~}%lboI1rKEyl&0e{V{ zEjD-yY zmEXS)^s8eeOrOZR``l`~>q(5{SdFC7upIB30u=9dr%%QbFE2_SQH2#n1b3(;v5&(T{ngfTC?-@C21_eOBrwjXz+uZ=cqXSBT^|VoVKL5VkF;m z<-s5!l84sw*4x_bMXwI)4_gYyj?E3^H?2<$tNq^$?IRMNEg%styQfV>T27)cT9(`` z)JLY~AGWI>EmSpZ`ofXQ9^#+Xnyv>nDtKN-XHw=;43Yu{)4mbP(R>?w89^CcquJ!` zRHvPxn$T#7up_r_ECV_Ee|gTq^VTx1pG!uTO#cga+SIx!STvx`1avE8>VHH|zp$TO zaSl2K>dWfmKc`_afRwAypb%fJU-ch!4Dy%jSDN{(pX?G^58}U4mS13Y|0kpOdHhFu zTzfqRPr|*+T(H0H=TcGg@@sr0Hd-{O6rn?qKiyvW5YU+jTgS4obOy}^QZg&;4SuS?bWdWy|wMnb5Bnb4)cZ}jH-1D#$B~c zgfT>YtB}2n7Uq{v$P&FD|5bO}t^HcamyU3w*% zT_!#7z=K7DyJ$TNNdq%dF)QTL04>S%AnIOOwS>SpLoApY{jQcYU4uWerfH%|Ju`S5 z5>p`nvG}2H#q_R)6M;u71zyN*ghk$Zn(W9k1u1lRW1>dlv7x{YT-km;jV$%N1?fy^ zCB}%;wjV8(ztbf@U>6N5tChcf;JQ&NUvhUHB1npiGZ{QL|#;V@}G-^tY*?@t+qUW;pz5Co8r(d zp{_%P>>ZX;PFE&SyGB~WA#qkd;LEHNUP-?CY^;@=Ch%e1V~p4%`GW%f1%mnt8G9}_ zX|hDF_98+3g{DgbO|dfdMNkoiJp!A>+pR@)-vZd)D-la>AbF50iuhRNaOA&EXfArz+@rp zJoo+AOq$3;ed6>J^sndw-Jz~9YP3MVCraj)J^PJ25iTjXggRPgOq0@&f$5#Ps83s;o_7@}qv z(=gE{;9Q){yJXBpX039DPH5FE2nw)DDHOA# zl1F8R%cl=hQCw;+0vdxToyNrj8k#(w;BhF4o)DXyodS#@0<@^?loB~sY4JT_S+sY# z#jH#IPi0>MO=TChZ3yQaQ%MqDQIXO>WGwSMr8*5FO^SHPP!y3d#gR-Y zM3D>;2~jGMN+rU#&*}UA);`wy+oyMF)w{Z`eeLPq_r9OK?|n(ptvqi38==$7L|9`6 z)gp5z$~gKG#vOePicQL6$2n3`4ZX;=Y^OtHE3+kC$7f|nRvHlk{_^|eb?jkk52jxx)SCLJw%YNF9um1Wp4YQj z2M%TqA!rvyMxa$!4KH=>J!dtyfW#vW@Uyo7$QqU4xujV$B5SzG) zkDSOZj%S~@Xlq!1b?~FGV|irZCCAzW>~@%kdS0AtX4)OY6)s2uO{aP`BP3EsM})xjOT5JU3?l%ta+2e|7|7_g4snFCyHBV4t-?58s)_xHg zGUrHk6=7tN*ca(i#n0-W-(AX)8g^t>vGMTXSaH2_bKhm10|Gaf&K-FreESyCIku!-Nks0)brR4F@&r}|fV=MMKW9N*#S*;|`5;BPoy zlTeu*u(MkEXTMdBNyNkd-Wp|Pdr$QKdiT9GH@7@38?$OGy5q6GmDQ1B-*C136E*!7 zi5ah-k+FJ zv2U+R4WB%j4d&bB2bmpBwhrE{lX8TyJxJaRb=Shp#ZzCITVf$UvEYJ11dAQ#F z#nXYT+KrL^-pd+KTDbVsdt9}+Ui>TQL`Js3%Au*ogO;^X{qwTtY!~YvR~i%B9mW|~ z(_)o48JlfozQpL`*`I82zdbTXYvz?ji2QLf7e1FcC`UGW?bhBs!FA6$X`m_uGa`rC zXEh00^;X%>d758}Cd)LADvT!ZWh7N-%%9sE7I1!R`raBtIniZl!v%fXrJt-PP5hWb zN8MhEt$Ao>ztuwTSiO}m>*S8GWksA9Gr2N`>rWmfj#l1yys0T~_jE!lOe&WUK*n6iRS;A#2)Vo#> z9Pj&}>Nl}U+>4Sie?{RMx$DPpr0j(j$`7Hpi4__*s@i8iX;Z%69ns7kJAZns@YJii zCkdSeTT6O&>WNMN$`6hWYtsAn$$wHk`M}zh(?cwBl#Rw6@;*khT4o*UWR1Yp;-qmm z4pzCrCjmm$GZRtdXB~%MDIOA=o0!RNm*3YH+NIq%a{6nVyt|cR+x-~wcFSA7ntVCh zw=(+$TBE*(Oh}*W4|0k0SBYGIF7lxrpO}4%W)Mg>W!B;GpSsJH)#;yDg3K+q)ecw4 zjegsiTescw#L&4+Yqw#G<>DjFho9>&>;ILs{X%ehD9f{wa)S>UaxNTCEvNlrzBKIS ze3$-%%_@`2{_cY{62B$mwmq?tO^?ZGy`@e`De}#H!<_^D+R3*J9&mQJPQ07m z)4$_Tv5LD@q=CoCE$`oR-hUVI@B zov{^KMtFGStZ(=8k3xIDU8)?4YcK7VsSH@>U$I#4Bu9aqjFexNP9lqy>NkP%`x+Zu zUDoU~aWA=ct~D^LMLM&WC*rQQ$yn^;SIub}2DT^Kj=9vSHAxK(4k?o|bBeOYJt&12 z)&-qS`Q0%6a<*{J+kCG`>SQOImmhidMLhXGR>N5HH?K-e&zJK`NfdrI@5qnVKYHER ztXxKseaJh3z!m1QS}|x#fH_9cZTyQNAQcLVN9l4g3B=%~2`ms+RDo^QQ+ zVQ!+rcC)@d733aC;f(Ec zk3U_pL2-8}zk;F2$JpngDu*?M^u)vW7Z-)SAgjq8HhrTn{M|&%!K&83ezKz8XN!wR zdDj(JGZSvk4)x4{_Q{&&pkiyTPZIu9uCn|M7S88(%)QneS&}UO&01rH-U=BXm7Q0% zUCz7raeQm&57zn0tRLOJFCFvmnI9LEG&;KR)UNjE^fhDaQ|DRl6pfy0E8Ti8jJql( zK+(3+l)TTPTuj7JIVqAW-~PI)=LMhmLz%;*mY5Ok>eiFd3EL|TvR7&geGxsrs`WQ# zUlhOmh4!Il3CfLMzq_*Qo-WSvmM-#C&NbbX6I)-t;nQg0#aFXVIUl3e2NHW&?|k1A z9bO&t`(e8n#}(E^KV-)JMl;^of5Mm-8Tfg}X!ex}k_(spF01Q4^62Z&&9lU2rB~-| z&Sy2rU0`ZhzIlmf%Od%X-mLN3!&l37n)|z+&ENFbyqMj|m!P@Vp~}RI*{ZoG@1A*v zLb8R}Si|XB?a;bS%H*iQ6xn>WkfnqCIbTnFXkODLXn5j`%+~Mi1$(1=<(wQ+`oE z&ppdq+JaA&#&u6@8{fWBR$oP^Jk&n0J#;1|;LQc`jQp&gzALY?6$+ju=!&jgr+v+L zr(}vD=jVakj4Ju?%WP`{C2wg&KCx?VmYyYCu3sbH|FFIO7kTROJ+rgjosr!Fvj@AI z(zk8W^1N52e;AL^TZY?=iq*zneDY%1+LL$sI@6v<+w`pG_U}KHTJSO2wl(x@jk5cX zb*B!bO!Y_%R}%GU=~pTFiwGsEs5wz77c>b?f~aSSBDN4X&`fp0o7)IfWCFpQ6R43fG8zWH8r4Zzi z%cB5^HY~`c7`y~oeIu?BGL|q@A&P4uVJ88(6k4;*PzY}r>mZ6~IiZ9Z^?cmieF6{i zkv^R&LJdz+l~M44@S7PmU*(HY0v`vexfA$C*p6=Q;=U6)G1POY_;Gd+sJ%H)C}2k&wG;r! zM<1}@Ukg(L379{6wr2nV!Cfir3`3;|P`*lIS?D&4R?5p&7zeTqp*9vjWu+q4$A#Kh zGzdQT2AC@&9ZZU`qHy0KU>=KgvGwSNizZP;55swp4VS?%$dgXoh9#pLE?Oz0yRboq zdWfIGy%z(93+)gvWy}VJ{1}WrG4NQs)b`P2Q=JA6V zGIPEg*2984v3=izv3=bSqzs75S7o<%A!m#)|qug)$I`@gZN@p@;=zGmB7d zsc|^wj=aFVI)cR^50m8Mm_0JHOawN}&a6PqHIFGesj28_IcYgjWY1#ytjtFEfeVw>`TPGJTmqEAPeLgsLdtGR#Z5t0Y*&zH$qeE0zwFy;CN-QQvG=jjKCaB=KSTM5TMl3Ny1>47gMl^>? zR^P%%J2*Ky;e*|nF#bLc|vvy?)k~6Vr=mR z)7>GB21ZnKz{v~077J#q@IKR%ckp^F+@tH#QPs#Nf=+?wjY1|WI3^KOqU)6W zY*g^iM6e=kg@%K5Au8B52~(j1SJj|`>#1OPEz)d71*=@al!=y*?8Ht~aMBe_oo)(* z_o9N?lfe|QfJ!#&Lk0UKV`_BZUqh(iJ}MZVxuj31VAB*J8wPJC;Lp=B5yihN{Qqy| z2jg9tI_5!&7-7681>>UYcs)KE8-n{ImI^u^wgW4dqk^MTF-4voQ1eIO>1h}VpkwQNa(dEhzbi9V%EM9h3~4R1-%U7-7IWx*hQH zaaQ(r2((YYF+q~yNBu}8VB<}Jg9%Tf)h?)lX41h7ge`5?K~%77#sW*5?2Za^I+$@VLscLu!;%}I8)2}(X;g3s6>JMl8s)R7;DH<1 zdg3MsY!godOZ?kn;x#S6QHfcZ;II)mdtW($!v`@nC!orp`WSd?Ch&$~yF&)94zS(` zW=}8+*d2JqdJH@hG;nwpuz0Y&HNK7t9?8NqiPliX-`qe2FS)@ihea!}b)GMPzuLpGQh zupYx)R0h!;Oq;kJYTS}MRB%iVra`wu>IQBf+L)Nl!Q|<-${+5ag7@D7t}eVco4ZE? zEBt#}Ir}>vQ1*B5#*bHG{G>GXf#sH<58i9wtc| z&Fa%5fbHuFuziJ{hjksOL^XNX3R3p^cBeo-CMG%X^eSwJ%6n);2#YGHA2@qW`~6Qf znSTfz*9O&m3x43`DRUfEU3C5ezjp5gD)`GJSe`;JVN3Dl1;D_d z=_;VIg#iX#I_=Zj7yy%S-V#ijt`A)-Q5nD}ScZeQg%{&)x523NetEDM14e@+!Ng<( zHK>slLzLj{VNRT@6!;Zz&vx0N(t!7fb?HJYX}+jn>dVCVhf*-G;JGDmf&m8FL&frR zj#17j1{g^A3(5l5sxqAc1_9g&JhFlBE(7brPN+k2^H6E1PaET&JM~J)i;hGc#%!U?Aa3%E2K0`}iq+Fg)QQjSV4m`oAG2ngf87 z%R%eH8>4{+8k5YweWrsB0e{>jybsXu(79f?cm+m6VexQwrNwda4flZ?UOb5XAKA26ejmCzs z2{f+&K06E!>_G(=Qo*qQc=I(4Ec@?%qCKMWr-@Z6K@-F7D7~~-p!Ye_8(RsBj2B-{twV-%-fvGS? z1%p(jB6wmgC>~~T1uy5I4Q-IVREtjg?E|P_`-cm(2Y8`^L0(gub|Pt=%^JB!V5d6- z_B(JrrlV2WK;BdOdKjEX1?N6mP>)4%s9=x~RRmY5ThIp=no$`d>lSF2d5#MHS+_v@ z%NM9%kU~|97jE*+wu|B%jD-u}@Y`VOh;Hx{zSRX5 z%ONlZ;7yl;U7`KqYFzmnL!1E^dr(F>cy)+_pa!-#Xsj!i7MK_(1oLU zLOi%ZAJTLcZO7Kq1){hNP{H6rmlR&w4*VB*@bm3PW#H~uF!*=xMFsnHfWZ&j*hU9b zFu0e)hmUrE;0C-6nsa4zzH_#{%f^Y!`9n}A< zV&?dqcnH`BID>E(Y>%cJQ3ZPT0(%61>w5nb75uz+!RP75c2uy|E8s)G>!%EO#R$!c zUwMTo^T406tMLte7zrW#NNqr6S}QulgCnUL7VnO!fC`PpERh1w20dC@7w; zLS5j>=X^W`roe7|22|_%by@E zQ{ba*%fVRd1A^eO7CMhA2Vws9;dMFy1_XiP9DSOoagk0bFMglcdv-r^XON?jR;Xx53QRW{83G z1{3Fn7o~X}1{nBpaMcjQ*}E~K=(vFK#57nbw}62TuMkSc3{irsh*ET-@=O?F0GAS_ z={m<~GeZpEdLjYe_Yx!GGea0Rof;)uR4#CDQHM^5lnW}DdV>*vJPaBGcCU6FV2DBY zEf{C;zS7Q(#(?n6Dj?vX^8bC>5oL{A!S)$gjW-}#2}e{mc%n)IcOZpu;ybXthizG* z4=RJ}J4}HW4tWduqk@~>f%p`>F6<1Tfl;h_kYk{yi*l$BzCRvYF51c^+fWMz{1o_s z^_T~v>H(K4`Dw1JXw{3*H>sd;CxH733p^8o%Es{?#JXUsnsEpf3~pnp(SdgyMFqd2 zg5iKoJQ)@I-v>;U4$O8E6%1}|3Q*lrBHsN0%v87|tRqlqR(u2l10Iqw(KIl^cfLT+ z097@b7boh;f4z4vSXEmWcyU_iP_=ye2v$~D3*jOv*m4w14|q+_j70^PjDnICzy%pVq#KA#lYYG2OUUkIRF3v delta 52540 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 Date: Tue, 19 Aug 2025 13:00:02 +0200 Subject: [PATCH 20/59] [release-branch.go1.25] internal/poll: don't call Seek for overlapped Windows handles Overlapped handles don't have the file pointer updated when performing I/O operations, so there is no need to call FD.Seek to reset the file pointer. Also, some overlapped file handles don't support seeking. See #74951. For #74951. Fixes #75111. Change-Id: I0edd53beed7d3862730f3b2ed5fe9ba490e66c06 Reviewed-on: https://go-review.googlesource.com/c/go/+/697295 Reviewed-by: Damien Neil LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov (cherry picked from commit 509d5f647ffc413bd874c2e2bf6d1b33f9bc0ac2) Reviewed-on: https://go-review.googlesource.com/c/go/+/704315 --- src/internal/poll/fd_windows.go | 40 ++++++++++++++++++++++++--------- src/os/os_windows_test.go | 28 +++++++++++++++++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/internal/poll/fd_windows.go b/src/internal/poll/fd_windows.go index 74188c057277ed..dd833afd24b0a0 100644 --- a/src/internal/poll/fd_windows.go +++ b/src/internal/poll/fd_windows.go @@ -636,12 +636,22 @@ func (fd *FD) Pread(b []byte, off int64) (int, error) { fd.l.Lock() defer fd.l.Unlock() - curoffset, err := syscall.Seek(fd.Sysfd, 0, io.SeekCurrent) - if err != nil { - return 0, err + if fd.isBlocking { + curoffset, err := syscall.Seek(fd.Sysfd, 0, io.SeekCurrent) + if err != nil { + return 0, err + } + defer syscall.Seek(fd.Sysfd, curoffset, io.SeekStart) + defer fd.setOffset(curoffset) + } else { + // Overlapped handles don't have the file pointer updated + // when performing I/O operations, so there is no need to + // call Seek to reset the file pointer. + // Also, some overlapped file handles don't support seeking. + // See https://go.dev/issues/74951. + curoffset := fd.offset + defer fd.setOffset(curoffset) } - defer syscall.Seek(fd.Sysfd, curoffset, io.SeekStart) - defer fd.setOffset(curoffset) o := &fd.rop o.InitBuf(b) fd.setOffset(off) @@ -852,12 +862,22 @@ func (fd *FD) Pwrite(buf []byte, off int64) (int, error) { fd.l.Lock() defer fd.l.Unlock() - curoffset, err := syscall.Seek(fd.Sysfd, 0, io.SeekCurrent) - if err != nil { - return 0, err + if fd.isBlocking { + curoffset, err := syscall.Seek(fd.Sysfd, 0, io.SeekCurrent) + if err != nil { + return 0, err + } + defer syscall.Seek(fd.Sysfd, curoffset, io.SeekStart) + defer fd.setOffset(curoffset) + } else { + // Overlapped handles don't have the file pointer updated + // when performing I/O operations, so there is no need to + // call Seek to reset the file pointer. + // Also, some overlapped file handles don't support seeking. + // See https://go.dev/issues/74951. + curoffset := fd.offset + defer fd.setOffset(curoffset) } - defer syscall.Seek(fd.Sysfd, curoffset, io.SeekStart) - defer fd.setOffset(curoffset) var ntotal int for { diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go index d9af25d4085c17..6f091c38184189 100644 --- a/src/os/os_windows_test.go +++ b/src/os/os_windows_test.go @@ -1883,6 +1883,34 @@ func TestFileOverlappedSeek(t *testing.T) { } } +func TestFileOverlappedReadAtVolume(t *testing.T) { + // Test that we can use File.ReadAt with an overlapped volume handle. + // See https://go.dev/issues/74951. + t.Parallel() + name := `\\.\` + filepath.VolumeName(t.TempDir()) + namep, err := syscall.UTF16PtrFromString(name) + if err != nil { + t.Fatal(err) + } + h, err := syscall.CreateFile(namep, + syscall.GENERIC_READ|syscall.GENERIC_WRITE, + syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_READ, + nil, syscall.OPEN_ALWAYS, syscall.FILE_FLAG_OVERLAPPED, 0) + if err != nil { + if errors.Is(err, syscall.ERROR_ACCESS_DENIED) { + t.Skip("skipping test: access denied") + } + t.Fatal(err) + } + f := os.NewFile(uintptr(h), name) + defer f.Close() + + var buf [0]byte + if _, err := f.ReadAt(buf[:], 0); err != nil { + t.Fatal(err) + } +} + func TestPipe(t *testing.T) { t.Parallel() r, w, err := os.Pipe() From f3dc4aac0b7585f1a40516e3ae1af524ecacc1f0 Mon Sep 17 00:00:00 2001 From: Steve Muir Date: Thu, 18 Sep 2025 07:54:57 -0700 Subject: [PATCH 21/59] [release-branch.go1.25] runtime: initialise debug settings much earlier in startup process This is necessary specifically to set the value of `debug.decoratemappings` sufficiently early in the startup sequence that all memory ranges allocated can be named appropriately using the new Linux-specific naming API introduced in #71546. Example output (on ARM64): https://gist.github.com/9muir/3667654b9c3f52e8be92756219371672 For: #75324 Fixes #75669 Change-Id: Ic0b16233f54a45adef1660c4d0df59af2f5af86a Reviewed-on: https://go-review.googlesource.com/c/go/+/703476 Auto-Submit: Michael Knyszek LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek (cherry picked from commit 300d9d2714164e455abc7990d52de9de6b084df1) Reviewed-on: https://go-review.googlesource.com/c/go/+/708359 Reviewed-by: Cherry Mui --- src/runtime/decoratemappings_test.go | 72 ++++++++++++++++++++++++++++ src/runtime/export_test.go | 4 ++ src/runtime/proc.go | 24 ++++++++-- src/runtime/runtime1.go | 15 +++--- src/runtime/set_vma_name_linux.go | 6 ++- src/runtime/set_vma_name_stub.go | 2 + 6 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 src/runtime/decoratemappings_test.go diff --git a/src/runtime/decoratemappings_test.go b/src/runtime/decoratemappings_test.go new file mode 100644 index 00000000000000..7d1121c125df08 --- /dev/null +++ b/src/runtime/decoratemappings_test.go @@ -0,0 +1,72 @@ +// Copyright 2025 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 runtime_test + +import ( + "os" + "regexp" + "runtime" + "testing" +) + +func validateMapLabels(t *testing.T, labels []string) { + // These are the specific region labels that need get added during the + // runtime phase. Hence they are the ones that need to be confirmed as + // present at the time the test reads its own region labels, which + // is sufficient to validate that the default `decoratemappings` value + // (enabled) was set early enough in the init process. + regions := map[string]bool{ + "allspans array": false, + "gc bits": false, + "heap": false, + "heap index": false, + "heap reservation": false, + "immortal metadata": false, + "page alloc": false, + "page alloc index": false, + "page summary": false, + "scavenge index": false, + } + for _, label := range labels { + if _, ok := regions[label]; !ok { + t.Logf("unexpected region label found: \"%s\"", label) + } + regions[label] = true + } + for label, found := range regions { + if !found { + t.Logf("region label missing: \"%s\"", label) + } + } +} + +func TestDecorateMappings(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("decoratemappings is only supported on Linux") + // /proc/self/maps is also Linux-specific + } + + var labels []string + if rawMaps, err := os.ReadFile("/proc/self/maps"); err != nil { + t.Fatalf("failed to read /proc/self/maps: %v", err) + } else { + t.Logf("maps:%s\n", string(rawMaps)) + matches := regexp.MustCompile("[^[]+ \\[anon: Go: (.+)\\]\n").FindAllSubmatch(rawMaps, -1) + for _, match_pair := range matches { + // match_pair consists of the matching substring and the parenthesized group + labels = append(labels, string(match_pair[1])) + } + } + t.Logf("DebugDecorateMappings: %v", *runtime.DebugDecorateMappings) + if *runtime.DebugDecorateMappings != 0 && runtime.SetVMANameSupported() { + validateMapLabels(t, labels) + } else { + if len(labels) > 0 { + t.Errorf("unexpected mapping labels present: %v", labels) + } else { + t.Skip("mapping labels absent as expected") + } + } +} diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go index 9a4611e26e52a2..6559ecec67bbde 100644 --- a/src/runtime/export_test.go +++ b/src/runtime/export_test.go @@ -1927,3 +1927,7 @@ func (t *TraceStackTable) Reset() { func TraceStack(gp *G, tab *TraceStackTable) { traceStack(0, gp, (*traceStackTable)(tab)) } + +var DebugDecorateMappings = &debug.decoratemappings + +func SetVMANameSupported() bool { return setVMANameSupported() } diff --git a/src/runtime/proc.go b/src/runtime/proc.go index b41bbe93cf57c7..d898c9668b36dd 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -789,7 +789,9 @@ func cpuinit(env string) { // getGodebugEarly extracts the environment variable GODEBUG from the environment on // Unix-like operating systems and returns it. This function exists to extract GODEBUG // early before much of the runtime is initialized. -func getGodebugEarly() string { +// +// Returns nil, false if OS doesn't provide env vars early in the init sequence. +func getGodebugEarly() (string, bool) { const prefix = "GODEBUG=" var env string switch GOOS { @@ -807,12 +809,16 @@ func getGodebugEarly() string { s := unsafe.String(p, findnull(p)) if stringslite.HasPrefix(s, prefix) { - env = gostring(p)[len(prefix):] + env = gostringnocopy(p)[len(prefix):] break } } + break + + default: + return "", false } - return env + return env, true } // The bootstrap sequence is: @@ -859,11 +865,14 @@ func schedinit() { // The world starts stopped. worldStopped() + godebug, parsedGodebug := getGodebugEarly() + if parsedGodebug { + parseRuntimeDebugVars(godebug) + } ticks.init() // run as early as possible moduledataverify() stackinit() mallocinit() - godebug := getGodebugEarly() cpuinit(godebug) // must run before alginit randinit() // must run before alginit, mcommoninit alginit() // maps, hash, rand must not be used before this call @@ -880,7 +889,12 @@ func schedinit() { goenvs() secure() checkfds() - parsedebugvars() + if !parsedGodebug { + // Some platforms, e.g., Windows, didn't make env vars available "early", + // so try again now. + parseRuntimeDebugVars(gogetenv("GODEBUG")) + } + finishDebugVarsSetup() gcinit() // Allocate stack space that can be used when crashing due to bad stack diff --git a/src/runtime/runtime1.go b/src/runtime/runtime1.go index 424745d2357dc9..15b546783b5e53 100644 --- a/src/runtime/runtime1.go +++ b/src/runtime/runtime1.go @@ -402,7 +402,7 @@ var dbgvars = []*dbgVar{ {name: "updatemaxprocs", value: &debug.updatemaxprocs, def: 1}, } -func parsedebugvars() { +func parseRuntimeDebugVars(godebug string) { // defaults debug.cgocheck = 1 debug.invalidptr = 1 @@ -420,12 +420,6 @@ func parsedebugvars() { } debug.traceadvanceperiod = defaultTraceAdvancePeriod - godebug := gogetenv("GODEBUG") - - p := new(string) - *p = godebug - godebugEnv.Store(p) - // apply runtime defaults, if any for _, v := range dbgvars { if v.def != 0 { @@ -437,7 +431,6 @@ func parsedebugvars() { } } } - // apply compile-time GODEBUG settings parsegodebug(godebugDefault, nil) @@ -463,6 +456,12 @@ func parsedebugvars() { if debug.gccheckmark > 0 { debug.asyncpreemptoff = 1 } +} + +func finishDebugVarsSetup() { + p := new(string) + *p = gogetenv("GODEBUG") + godebugEnv.Store(p) setTraceback(gogetenv("GOTRACEBACK")) traceback_env = traceback_cache diff --git a/src/runtime/set_vma_name_linux.go b/src/runtime/set_vma_name_linux.go index 100c2bfecac60c..792d6ad34d9102 100644 --- a/src/runtime/set_vma_name_linux.go +++ b/src/runtime/set_vma_name_linux.go @@ -14,9 +14,13 @@ import ( var prSetVMAUnsupported atomic.Bool +func setVMANameSupported() bool { + return !prSetVMAUnsupported.Load() +} + // setVMAName calls prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, start, len, name) func setVMAName(start unsafe.Pointer, length uintptr, name string) { - if debug.decoratemappings == 0 || prSetVMAUnsupported.Load() { + if debug.decoratemappings == 0 || !setVMANameSupported() { return } diff --git a/src/runtime/set_vma_name_stub.go b/src/runtime/set_vma_name_stub.go index 38f65fd592ba34..6cb01ebf50dcef 100644 --- a/src/runtime/set_vma_name_stub.go +++ b/src/runtime/set_vma_name_stub.go @@ -10,3 +10,5 @@ import "unsafe" // setVMAName isn’t implemented func setVMAName(start unsafe.Pointer, len uintptr, name string) {} + +func setVMANameSupported() bool { return false } From 16fdaac4b164e7aa3fc97c60a6b20b1da3bd0cea Mon Sep 17 00:00:00 2001 From: Michael Pratt Date: Thu, 25 Sep 2025 10:12:12 -0700 Subject: [PATCH 22/59] [release-branch.go1.25] sync/atomic: correct Uintptr.Or return doc Uintptr.Or returns the old value, just like all of the other Or functions. This was a typo in the original CL 544455. For #75607. Fixes #75610. Change-Id: I260959e7e32e51f1152b5271df6cc51adfa02a4d Reviewed-on: https://go-review.googlesource.com/c/go/+/706816 Reviewed-by: Michael Knyszek Reviewed-by: Mauri de Souza Meneguzzo LUCI-TryBot-Result: Go LUCI Auto-Submit: Michael Pratt (cherry picked from commit d70ad4e740e24b4b76961c4b56d698fa23668aa2) Reviewed-on: https://go-review.googlesource.com/c/go/+/706856 --- src/sync/atomic/type.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sync/atomic/type.go b/src/sync/atomic/type.go index 40a29fed8ce3bb..4a74150b41bace 100644 --- a/src/sync/atomic/type.go +++ b/src/sync/atomic/type.go @@ -232,7 +232,7 @@ func (x *Uintptr) Add(delta uintptr) (new uintptr) { return AddUintptr(&x.v, del func (x *Uintptr) And(mask uintptr) (old uintptr) { return AndUintptr(&x.v, mask) } // Or atomically performs a bitwise OR operation on x using the bitmask -// provided as mask and returns the updated value after the OR operation. +// provided as mask and returns the old value. func (x *Uintptr) Or(mask uintptr) (old uintptr) { return OrUintptr(&x.v, mask) } // noCopy may be added to structs which must not be copied From 205d0865958a6d2342939f62dfeaf47508101976 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Mon, 29 Sep 2025 10:11:56 -0700 Subject: [PATCH 23/59] [release-branch.go1.25] crypto/tls: quote protocols in ALPN error message Quote the protocols sent by the client when returning the ALPN negotiation error message. Fixes CVE-2025-58189 Updates #75652 Fixes #75661 Change-Id: Ie7b3a1ed0b6efcc1705b71f0f1e8417126661330 Reviewed-on: https://go-review.googlesource.com/c/go/+/707776 Auto-Submit: Roland Shoemaker Reviewed-by: Neal Patel Reviewed-by: Nicholas Husin Auto-Submit: Nicholas Husin Reviewed-by: Nicholas Husin TryBot-Bypass: Roland Shoemaker Reviewed-by: Daniel McCarney (cherry picked from commit 4e9006a716533fe1c7ee08df02dfc73078f7dc19) Reviewed-on: https://go-review.googlesource.com/c/go/+/708095 Reviewed-by: Carlos Amedee LUCI-TryBot-Result: Go LUCI --- src/crypto/tls/handshake_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/tls/handshake_server.go b/src/crypto/tls/handshake_server.go index 1e0b5f06672d15..088c66fadb2a44 100644 --- a/src/crypto/tls/handshake_server.go +++ b/src/crypto/tls/handshake_server.go @@ -357,7 +357,7 @@ func negotiateALPN(serverProtos, clientProtos []string, quic bool) (string, erro if http11fallback { return "", nil } - return "", fmt.Errorf("tls: client requested unsupported application protocols (%s)", clientProtos) + return "", fmt.Errorf("tls: client requested unsupported application protocols (%q)", clientProtos) } // supportsECDHE returns whether ECDHE key exchanges can be used with this From 7735dc90ed6f157a7a9681dd77a0b05f0f098e78 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 23 Sep 2025 16:31:26 -0700 Subject: [PATCH 24/59] [release-branch.go1.25] cmd/compile: don't rely on loop info when there are irreducible loops Loop information is sketchy when there are irreducible loops. Sometimes blocks inside 2 loops can be recorded as only being part of the outer loop. That causes tighten to move values that want to move into such a block to move out of the loop altogether, breaking the invariant that operations have to be scheduled after their args. Fixes #75595 Change-Id: Idd80e6d2268094b8ae6387563081fdc1e211856a Reviewed-on: https://go-review.googlesource.com/c/go/+/706355 Reviewed-by: David Chase LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall (cherry picked from commit f15cd63ec4860c4f2c23cc992843546e0265c332) Reviewed-on: https://go-review.googlesource.com/c/go/+/706576 --- src/cmd/compile/internal/ssa/tighten.go | 27 +++++---- test/fixedbugs/issue75569.go | 77 +++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 test/fixedbugs/issue75569.go diff --git a/src/cmd/compile/internal/ssa/tighten.go b/src/cmd/compile/internal/ssa/tighten.go index eb5007b26e29e4..0a4b56d5781a24 100644 --- a/src/cmd/compile/internal/ssa/tighten.go +++ b/src/cmd/compile/internal/ssa/tighten.go @@ -124,18 +124,21 @@ func tighten(f *Func) { // If the target location is inside a loop, // move the target location up to just before the loop head. - for _, b := range f.Blocks { - origloop := loops.b2l[b.ID] - for _, v := range b.Values { - t := target[v.ID] - if t == nil { - continue - } - targetloop := loops.b2l[t.ID] - for targetloop != nil && (origloop == nil || targetloop.depth > origloop.depth) { - t = idom[targetloop.header.ID] - target[v.ID] = t - targetloop = loops.b2l[t.ID] + if !loops.hasIrreducible { + // Loop info might not be correct for irreducible loops. See issue 75569. + for _, b := range f.Blocks { + origloop := loops.b2l[b.ID] + for _, v := range b.Values { + t := target[v.ID] + if t == nil { + continue + } + targetloop := loops.b2l[t.ID] + for targetloop != nil && (origloop == nil || targetloop.depth > origloop.depth) { + t = idom[targetloop.header.ID] + target[v.ID] = t + targetloop = loops.b2l[t.ID] + } } } } diff --git a/test/fixedbugs/issue75569.go b/test/fixedbugs/issue75569.go new file mode 100644 index 00000000000000..8420641db2234a --- /dev/null +++ b/test/fixedbugs/issue75569.go @@ -0,0 +1,77 @@ +// run + +// Copyright 2025 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 main + +func fff(a []int, b bool, p, q *int) { +outer: + n := a[0] + a = a[1:] + switch n { + case 1: + goto one + case 2: + goto two + case 3: + goto three + case 4: + goto four + } + +one: + goto inner +two: + goto outer +three: + goto inner +four: + goto innerSideEntry + +inner: + n = a[0] + a = a[1:] + switch n { + case 1: + goto outer + case 2: + goto inner + case 3: + goto innerSideEntry + default: + return + } +innerSideEntry: + n = a[0] + a = a[1:] + switch n { + case 1: + goto outer + case 2: + goto inner + case 3: + goto inner + } + ggg(p, q) + goto inner +} + +var b bool + +func ggg(p, q *int) { + n := *p + 5 // this +5 ends up in the entry block, well before the *p load + if b { + *q = 0 + } + *p = n +} + +func main() { + var x, y int + fff([]int{4, 4, 4}, false, &x, &y) + if x != 5 { + panic(x) + } +} From 0b53e410f8f5cd1341ea492914d9b7fd17f3f6c1 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 1 Sep 2025 09:18:08 -0700 Subject: [PATCH 25/59] [release-branch.go1.25] debug/pe: permit symbols with no name They are reportedly generated by llvm-mingw clang21. For #75219 Fixes #75221 Change-Id: I7fa7e13039bc7eee826cc19826985ca0e357a9ff Reviewed-on: https://go-review.googlesource.com/c/go/+/700137 Reviewed-by: Cherry Mui Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Quim Muntal Auto-Submit: Ian Lance Taylor (cherry picked from commit ea00650784bc2909580c7decf729f668349aa939) Reviewed-on: https://go-review.googlesource.com/c/go/+/708356 --- src/debug/pe/symbol.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/debug/pe/symbol.go b/src/debug/pe/symbol.go index 6e8d9d16c22540..80acebe9f1f40b 100644 --- a/src/debug/pe/symbol.go +++ b/src/debug/pe/symbol.go @@ -98,7 +98,12 @@ func readCOFFSymbols(fh *FileHeader, r io.ReadSeeker) ([]COFFSymbol, error) { // isSymNameOffset checks symbol name if it is encoded as offset into string table. func isSymNameOffset(name [8]byte) (bool, uint32) { if name[0] == 0 && name[1] == 0 && name[2] == 0 && name[3] == 0 { - return true, binary.LittleEndian.Uint32(name[4:]) + offset := binary.LittleEndian.Uint32(name[4:]) + if offset == 0 { + // symbol has no name + return false, 0 + } + return true, offset } return false, 0 } From 06993c7721600e35a28aa032081fe2d37690de5d Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Thu, 18 Sep 2025 11:15:47 -0700 Subject: [PATCH 26/59] [release-branch.go1.25] context: don't return a non-nil from Err before Done is closed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Context.Err documentation states that it returns nil if the context's done channel is not closed. Fix a race condition introduced by CL 653795 where Err could return a non-nil error slightly before the Done channel is closed. No impact on Err performance when returning nil. Slows down Err when returning non-nil by about 3x, but that's still almost 2x faster than before CL 653795 and the performance of this path is less important. (A tight loop checking Err for doneness will be terminated by the first Err call to return a non-nil result.) goos: darwin goarch: arm64 pkg: context cpu: Apple M4 Pro │ /tmp/bench.0 │ /tmp/bench.1 │ │ sec/op │ sec/op vs base │ ErrOK-14 1.806n ± 1% 1.774n ± 0% -1.77% (p=0.000 n=8) ErrCanceled-14 1.821n ± 1% 7.525n ± 3% +313.23% (p=0.000 n=8) geomean 1.813n 3.654n +101.47% Fixes #75533 Fixes #75537 Change-Id: Iea22781a199ace7e7f70cf65168c36e090cd2e2a Reviewed-on: https://go-review.googlesource.com/c/go/+/705235 TryBot-Bypass: Damien Neil Reviewed-by: Nicholas Husin Reviewed-by: Nicholas Husin Auto-Submit: Damien Neil (cherry picked from commit 8ca209ec3962874ad1c15c22c86293edf428c284) Reviewed-on: https://go-review.googlesource.com/c/go/+/705375 LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- src/context/context.go | 2 ++ src/context/x_test.go | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/context/context.go b/src/context/context.go index 4f150f6a1d6c7e..24bb18abd3dc27 100644 --- a/src/context/context.go +++ b/src/context/context.go @@ -463,6 +463,8 @@ func (c *cancelCtx) Done() <-chan struct{} { func (c *cancelCtx) Err() error { // An atomic load is ~5x faster than a mutex, which can matter in tight loops. if err := c.err.Load(); err != nil { + // Ensure the done channel has been closed before returning a non-nil error. + <-c.Done() return err.(error) } return nil diff --git a/src/context/x_test.go b/src/context/x_test.go index 937cab1445e7e8..0cf19688c3f5ee 100644 --- a/src/context/x_test.go +++ b/src/context/x_test.go @@ -1177,3 +1177,23 @@ func (c *customContext) Err() error { func (c *customContext) Value(key any) any { return c.parent.Value(key) } + +// Issue #75533. +func TestContextErrDoneRace(t *testing.T) { + // 4 iterations reliably reproduced #75533. + for range 10 { + ctx, cancel := WithCancel(Background()) + donec := ctx.Done() + go cancel() + for ctx.Err() == nil { + if runtime.GOARCH == "wasm" { + runtime.Gosched() // need to explicitly yield + } + } + select { + case <-donec: + default: + t.Fatalf("ctx.Err is non-nil, but ctx.Done is not closed") + } + } +} From 28ac8d210426f374f69650958bbb20ff800e0aea Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Mon, 15 Sep 2025 15:18:57 -0700 Subject: [PATCH 27/59] [release-branch.go1.25] net/http: avoid connCount underflow race Remove a race condition in counting the number of connections per host, which can cause a connCount underflow and a panic. The race occurs when: - A RoundTrip call attempts to use a HTTP/2 roundtripper (pconn.alt != nil) and receives an isNoCachedConn error. The call removes the pconn from the idle conn pool and decrements the connCount for its host. - A second RoundTrip call on the same pconn succeeds, and delivers the pconn to a third RoundTrip waiting for a conn. - The third RoundTrip receives the pconn at the same moment its request context is canceled. It places the pconn back into the idle conn pool. At this time, the connCount is incorrect, because the conn returned to the idle pool is not matched by an increment in the connCount. Fix this by not adding HTTP/2 pconns back to the idle pool in wantConn.cancel. For #61474 Fixes #75539 Change-Id: I104d6cf85a54d0382eebf3fcf5dda99c69a7c3f6 Reviewed-on: https://go-review.googlesource.com/c/go/+/703936 Auto-Submit: Damien Neil Reviewed-by: Nicholas Husin Reviewed-by: Nicholas Husin LUCI-TryBot-Result: Go LUCI (cherry picked from commit 3203a5da290753e5c7aceb12f41f06b272356bd0) Reviewed-on: https://go-review.googlesource.com/c/go/+/705376 Reviewed-by: Cherry Mui --- src/net/http/transport.go | 5 ++++- src/net/http/transport_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 07b3a9e1e72ba6..2778db332e4319 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -1372,7 +1372,10 @@ func (w *wantConn) cancel(t *Transport) { w.done = true w.mu.Unlock() - if pc != nil { + // HTTP/2 connections (pc.alt != nil) aren't removed from the idle pool on use, + // and should not be added back here. If the pconn isn't in the idle pool, + // it's because we removed it due to an error. + if pc != nil && pc.alt == nil { t.putOrCloseIdleConn(pc) } } diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index 9762f058867dc1..28ad3eb6feb54f 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -7559,3 +7559,35 @@ func TestTransportServerProtocols(t *testing.T) { }) } } + +func TestIssue61474(t *testing.T) { + run(t, testIssue61474, []testMode{http2Mode}) +} +func testIssue61474(t *testing.T, mode testMode) { + if testing.Short() { + return + } + + // This test reliably exercises the condition causing #61474, + // but requires many iterations to do so. + // Keep the test around for now, but don't run it by default. + t.Skip("test is too large") + + cst := newClientServerTest(t, mode, HandlerFunc(func(rw ResponseWriter, req *Request) { + }), func(tr *Transport) { + tr.MaxConnsPerHost = 1 + }) + var wg sync.WaitGroup + defer wg.Wait() + for range 100000 { + wg.Go(func() { + ctx, cancel := context.WithTimeout(t.Context(), 1*time.Millisecond) + defer cancel() + req, _ := NewRequestWithContext(ctx, "GET", cst.ts.URL, nil) + resp, err := cst.c.Do(req) + if err == nil { + resp.Body.Close() + } + }) + } +} From d6f274124871dbc3e55b9101e2891785500a178c Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Mon, 6 Oct 2025 10:36:13 -0700 Subject: [PATCH 28/59] [release-branch.go1.25] spec: update spec date to match release date Ths spec date in the 1.25 release branch dates back to Feb 2025 which is rather confusing. Moving it forward to 1.25 release date. Fixes #75743. Change-Id: Ibb2da5dc238a3b876862eef802e90bee6326d6b5 Reviewed-on: https://go-review.googlesource.com/c/go/+/709515 Auto-Submit: Robert Griesemer LUCI-TryBot-Result: Go LUCI Reviewed-by: Alan Donovan Reviewed-by: Robert Griesemer --- doc/go_spec.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/go_spec.html b/doc/go_spec.html index 183bc7fb372755..2b47fb2e834d83 100644 --- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -1,6 +1,6 @@ From 66f6feaa53c0aa368e8f203e81b7a4d0e002da36 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Mon, 6 Oct 2025 11:24:00 -0700 Subject: [PATCH 29/59] [release-branch.go1.25] spec: revert "update spec date to match release date" This reverts CL 709515 (commit d6f274124871dbc3e55b9101e2891785500a178c). Reason for revert: Minor release is in process. Let's hold off with this until the minor release is out. Change-Id: Ie6ee91cb61836f8b3494fb895ef4b9976f54dd1d Reviewed-on: https://go-review.googlesource.com/c/go/+/709535 Reviewed-by: Robert Griesemer LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov Reviewed-by: Michael Pratt Auto-Submit: Michael Pratt --- doc/go_spec.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/go_spec.html b/doc/go_spec.html index 2b47fb2e834d83..183bc7fb372755 100644 --- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -1,6 +1,6 @@ From 6a057327cf9a405e6388593dd4aedc0d0da77092 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Thu, 25 Sep 2025 14:41:53 -0700 Subject: [PATCH 30/59] [release-branch.go1.25] net/mail: avoid quadratic behavior in mail address parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RFC 5322 domain-literal parsing built the dtext value one character at a time with string concatenation, resulting in excessive resource consumption when parsing very large domain-literal values. Replace with a subslice. Benchmark not included in this CL because it's too narrow to be of general ongoing use, but for: ParseAddress("alice@[" + strings.Repeat("a", 0x40000) + "]") goos: darwin goarch: arm64 pkg: net/mail cpu: Apple M4 Pro │ /tmp/bench.0 │ /tmp/bench.1 │ │ sec/op │ sec/op vs base │ ParseAddress-14 1987.732m ± 9% 1.524m ± 5% -99.92% (p=0.000 n=10) │ /tmp/bench.0 │ /tmp/bench.1 │ │ B/op │ B/op vs base │ ParseAddress-14 33692.767Mi ± 0% 1.282Mi ± 0% -100.00% (p=0.000 n=10) │ /tmp/bench.0 │ /tmp/bench.1 │ │ allocs/op │ allocs/op vs base │ ParseAddress-14 263711.00 ± 0% 17.00 ± 0% -99.99% (p=0.000 n=10) Thanks to Philippe Antoine (Catena cyber) for reporting this issue. Fixes CVE-2025-61725 For #75680 Fixes #75701 Change-Id: Id971c2d5b59882bb476e22fceb7e01ec08234bb7 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2840 Reviewed-by: Roland Shoemaker Reviewed-by: Nicholas Husin Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2961 Reviewed-by: Damien Neil Reviewed-on: https://go-review.googlesource.com/c/go/+/709844 TryBot-Bypass: Michael Pratt Auto-Submit: Michael Pratt Reviewed-by: Carlos Amedee --- src/net/mail/message.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/net/mail/message.go b/src/net/mail/message.go index 14f839a03077c1..1502b3596252ba 100644 --- a/src/net/mail/message.go +++ b/src/net/mail/message.go @@ -724,7 +724,8 @@ func (p *addrParser) consumeDomainLiteral() (string, error) { } // Parse the dtext - var dtext string + dtext := p.s + dtextLen := 0 for { if p.empty() { return "", errors.New("mail: unclosed domain-literal") @@ -741,9 +742,10 @@ func (p *addrParser) consumeDomainLiteral() (string, error) { return "", fmt.Errorf("mail: bad character in domain-literal: %q", r) } - dtext += p.s[:size] + dtextLen += size p.s = p.s[size:] } + dtext = dtext[:dtextLen] // Skip the trailing ] if !p.consume(']') { From 930ce220d052d632f0d84df5850c812a77b70175 Mon Sep 17 00:00:00 2001 From: Neal Patel Date: Thu, 11 Sep 2025 16:27:04 -0400 Subject: [PATCH 31/59] [release-branch.go1.25] crypto/x509: mitigate DoS vector when intermediate certificate contains DSA public key An attacker could craft an intermediate X.509 certificate containing a DSA public key and can crash a remote host with an unauthenticated call to any endpoint that verifies the certificate chain. Thank you to Jakub Ciolek for reporting this issue. Fixes CVE-2025-58188 For #75675 Fixes #75703 Change-Id: I2ecbb87b9b8268dbc55c8795891e596ab60f0088 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2780 Reviewed-by: Damien Neil Reviewed-by: Roland Shoemaker Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2963 Commit-Queue: Roland Shoemaker Reviewed-on: https://go-review.googlesource.com/c/go/+/709845 TryBot-Bypass: Michael Pratt Reviewed-by: Carlos Amedee Auto-Submit: Michael Pratt --- src/crypto/x509/verify.go | 5 +- src/crypto/x509/verify_test.go | 127 +++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index 7cc0fb2e3e0385..755c1db96c1edb 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -927,7 +927,10 @@ func alreadyInChain(candidate *Certificate, chain []*Certificate) bool { if !bytes.Equal(candidate.RawSubject, cert.RawSubject) { continue } - if !candidate.PublicKey.(pubKeyEqual).Equal(cert.PublicKey) { + // We enforce the canonical encoding of SPKI (by only allowing the + // correct AI paremeter encodings in parseCertificate), so it's safe to + // directly compare the raw bytes. + if !bytes.Equal(candidate.RawSubjectPublicKeyInfo, cert.RawSubjectPublicKeyInfo) { continue } var certSAN *pkix.Extension diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go index 7991f49946d587..5595f99ea5e43a 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -6,6 +6,7 @@ package x509 import ( "crypto" + "crypto/dsa" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -3048,3 +3049,129 @@ func TestInvalidPolicyWithAnyKeyUsage(t *testing.T) { t.Fatalf("unexpected error, got %q, want %q", err, expectedErr) } } + +func TestCertificateChainSignedByECDSA(t *testing.T) { + caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + root := &Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "X"}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(365 * 24 * time.Hour), + IsCA: true, + KeyUsage: KeyUsageCertSign | KeyUsageCRLSign, + BasicConstraintsValid: true, + } + caDER, err := CreateCertificate(rand.Reader, root, root, &caKey.PublicKey, caKey) + if err != nil { + t.Fatal(err) + } + root, err = ParseCertificate(caDER) + if err != nil { + t.Fatal(err) + } + + leafKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + leaf := &Certificate{ + SerialNumber: big.NewInt(42), + Subject: pkix.Name{CommonName: "leaf"}, + NotBefore: time.Now().Add(-10 * time.Minute), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: KeyUsageDigitalSignature, + ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + leafDER, err := CreateCertificate(rand.Reader, leaf, root, &leafKey.PublicKey, caKey) + if err != nil { + t.Fatal(err) + } + leaf, err = ParseCertificate(leafDER) + if err != nil { + t.Fatal(err) + } + + inter, err := ParseCertificate(dsaSelfSignedCNX(t)) + if err != nil { + t.Fatal(err) + } + + inters := NewCertPool() + inters.AddCert(root) + inters.AddCert(inter) + + wantErr := "certificate signed by unknown authority" + _, err = leaf.Verify(VerifyOptions{Intermediates: inters, Roots: NewCertPool()}) + if !strings.Contains(err.Error(), wantErr) { + t.Errorf("got %v, want %q", err, wantErr) + } +} + +// dsaSelfSignedCNX produces DER-encoded +// certificate with the properties: +// +// Subject=Issuer=CN=X +// DSA SPKI +// Matching inner/outer signature OIDs +// Dummy ECDSA signature +func dsaSelfSignedCNX(t *testing.T) []byte { + t.Helper() + var params dsa.Parameters + if err := dsa.GenerateParameters(¶ms, rand.Reader, dsa.L1024N160); err != nil { + t.Fatal(err) + } + + var dsaPriv dsa.PrivateKey + dsaPriv.Parameters = params + if err := dsa.GenerateKey(&dsaPriv, rand.Reader); err != nil { + t.Fatal(err) + } + dsaPub := &dsaPriv.PublicKey + + type dsaParams struct{ P, Q, G *big.Int } + paramDER, err := asn1.Marshal(dsaParams{dsaPub.P, dsaPub.Q, dsaPub.G}) + if err != nil { + t.Fatal(err) + } + yDER, err := asn1.Marshal(dsaPub.Y) + if err != nil { + t.Fatal(err) + } + + spki := publicKeyInfo{ + Algorithm: pkix.AlgorithmIdentifier{ + Algorithm: oidPublicKeyDSA, + Parameters: asn1.RawValue{FullBytes: paramDER}, + }, + PublicKey: asn1.BitString{Bytes: yDER, BitLength: 8 * len(yDER)}, + } + + rdn := pkix.Name{CommonName: "X"}.ToRDNSequence() + b, err := asn1.Marshal(rdn) + if err != nil { + t.Fatal(err) + } + rawName := asn1.RawValue{FullBytes: b} + + algoIdent := pkix.AlgorithmIdentifier{Algorithm: oidSignatureDSAWithSHA256} + tbs := tbsCertificate{ + Version: 0, + SerialNumber: big.NewInt(1002), + SignatureAlgorithm: algoIdent, + Issuer: rawName, + Validity: validity{NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(24 * time.Hour)}, + Subject: rawName, + PublicKey: spki, + } + c := certificate{ + TBSCertificate: tbs, + SignatureAlgorithm: algoIdent, + SignatureValue: asn1.BitString{Bytes: []byte{0}, BitLength: 8}, + } + dsaDER, err := asn1.Marshal(c) + if err != nil { + t.Fatal(err) + } + return dsaDER +} From 5d7a787aa2b486f77537eeaed9c38c940a7182b8 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Tue, 30 Sep 2025 15:11:16 -0700 Subject: [PATCH 32/59] [release-branch.go1.25] net/textproto: avoid quadratic complexity in Reader.ReadResponse Reader.ReadResponse constructed a response string from repeated string concatenation, permitting a malicious sender to cause excessive memory allocation and CPU consumption by sending a response consisting of many short lines. Use a strings.Builder to construct the string instead. Thanks to Jakub Ciolek for reporting this issue. Fixes CVE-2025-61724 For #75716 Fixes #75718 Change-Id: I1a98ce85a21b830cb25799f9ac9333a67400d736 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2940 Reviewed-by: Roland Shoemaker Reviewed-by: Nicholas Husin Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2960 Reviewed-by: Damien Neil Reviewed-on: https://go-review.googlesource.com/c/go/+/709846 Reviewed-by: Carlos Amedee TryBot-Bypass: Michael Pratt Auto-Submit: Michael Pratt --- src/net/textproto/reader.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go index d375340121fa0b..94e3e0b53d115e 100644 --- a/src/net/textproto/reader.go +++ b/src/net/textproto/reader.go @@ -284,8 +284,10 @@ func (r *Reader) ReadCodeLine(expectCode int) (code int, message string, err err // // An expectCode <= 0 disables the check of the status code. func (r *Reader) ReadResponse(expectCode int) (code int, message string, err error) { - code, continued, message, err := r.readCodeLine(expectCode) + code, continued, first, err := r.readCodeLine(expectCode) multi := continued + var messageBuilder strings.Builder + messageBuilder.WriteString(first) for continued { line, err := r.ReadLine() if err != nil { @@ -296,12 +298,15 @@ func (r *Reader) ReadResponse(expectCode int) (code int, message string, err err var moreMessage string code2, continued, moreMessage, err = parseCodeLine(line, 0) if err != nil || code2 != code { - message += "\n" + strings.TrimRight(line, "\r\n") + messageBuilder.WriteByte('\n') + messageBuilder.WriteString(strings.TrimRight(line, "\r\n")) continued = true continue } - message += "\n" + moreMessage + messageBuilder.WriteByte('\n') + messageBuilder.WriteString(moreMessage) } + message = messageBuilder.String() if err != nil && multi && message != "" { // replace one line error message with all lines (full message) err = &Error{code, message} From 9fd3ac8a10272afd90312fef5d379de7d688a58e Mon Sep 17 00:00:00 2001 From: Ethan Lee Date: Fri, 29 Aug 2025 17:35:55 +0000 Subject: [PATCH 33/59] [release-branch.go1.25] net/url: enforce stricter parsing of bracketed IPv6 hostnames - Previously, url.Parse did not enforce validation of hostnames within square brackets. - RFC 3986 stipulates that only IPv6 hostnames can be embedded within square brackets in a URL. - Now, the parsing logic should strictly enforce that only IPv6 hostnames can be resolved when in square brackets. IPv4, IPv4-mapped addresses and other input will be rejected. - Update url_test to add test cases that cover the above scenarios. Thanks to Enze Wang, Jingcheng Yang and Zehui Miao of Tsinghua University for reporting this issue. Fixes CVE-2025-47912 For #75678 Fixes #75713 Change-Id: Iaa41432bf0ee86de95a39a03adae5729e4deb46c Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2680 Reviewed-by: Damien Neil Reviewed-by: Roland Shoemaker Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2988 Commit-Queue: Roland Shoemaker Reviewed-on: https://go-review.googlesource.com/c/go/+/709847 TryBot-Bypass: Michael Pratt Auto-Submit: Michael Pratt Reviewed-by: Carlos Amedee --- src/go/build/deps_test.go | 10 ++++++---- src/net/url/url.go | 42 +++++++++++++++++++++++++++++---------- src/net/url/url_test.go | 39 ++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 14 deletions(-) diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 6d92542e31b652..b3ca2a017edd26 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -235,7 +235,6 @@ var depsRules = ` internal/types/errors, mime/quotedprintable, net/internal/socktest, - net/url, runtime/trace, text/scanner, text/tabwriter; @@ -298,6 +297,12 @@ var depsRules = ` FMT < text/template/parse; + internal/bytealg, internal/itoa, math/bits, slices, strconv, unique + < net/netip; + + FMT, net/netip + < net/url; + net/url, text/template/parse < text/template < internal/lazytemplate; @@ -412,9 +417,6 @@ var depsRules = ` < golang.org/x/net/dns/dnsmessage, golang.org/x/net/lif; - internal/bytealg, internal/itoa, math/bits, slices, strconv, unique - < net/netip; - os, net/netip < internal/routebsd; diff --git a/src/net/url/url.go b/src/net/url/url.go index 2a57659460373d..40faa7cb9e1c6f 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -16,6 +16,7 @@ import ( "errors" "fmt" "maps" + "net/netip" "path" "slices" "strconv" @@ -626,40 +627,61 @@ func parseAuthority(authority string) (user *Userinfo, host string, err error) { // parseHost parses host as an authority without user // information. That is, as host[:port]. func parseHost(host string) (string, error) { - if strings.HasPrefix(host, "[") { + if openBracketIdx := strings.LastIndex(host, "["); openBracketIdx != -1 { // Parse an IP-Literal in RFC 3986 and RFC 6874. // E.g., "[fe80::1]", "[fe80::1%25en0]", "[fe80::1]:80". - i := strings.LastIndex(host, "]") - if i < 0 { + closeBracketIdx := strings.LastIndex(host, "]") + if closeBracketIdx < 0 { return "", errors.New("missing ']' in host") } - colonPort := host[i+1:] + + colonPort := host[closeBracketIdx+1:] if !validOptionalPort(colonPort) { return "", fmt.Errorf("invalid port %q after host", colonPort) } + unescapedColonPort, err := unescape(colonPort, encodeHost) + if err != nil { + return "", err + } + hostname := host[openBracketIdx+1 : closeBracketIdx] + var unescapedHostname string // RFC 6874 defines that %25 (%-encoded percent) introduces // the zone identifier, and the zone identifier can use basically // any %-encoding it likes. That's different from the host, which // can only %-encode non-ASCII bytes. // We do impose some restrictions on the zone, to avoid stupidity // like newlines. - zone := strings.Index(host[:i], "%25") - if zone >= 0 { - host1, err := unescape(host[:zone], encodeHost) + zoneIdx := strings.Index(hostname, "%25") + if zoneIdx >= 0 { + hostPart, err := unescape(hostname[:zoneIdx], encodeHost) if err != nil { return "", err } - host2, err := unescape(host[zone:i], encodeZone) + zonePart, err := unescape(hostname[zoneIdx:], encodeZone) if err != nil { return "", err } - host3, err := unescape(host[i:], encodeHost) + unescapedHostname = hostPart + zonePart + } else { + var err error + unescapedHostname, err = unescape(hostname, encodeHost) if err != nil { return "", err } - return host1 + host2 + host3, nil } + + // Per RFC 3986, only a host identified by a valid + // IPv6 address can be enclosed by square brackets. + // This excludes any IPv4 or IPv4-mapped addresses. + addr, err := netip.ParseAddr(unescapedHostname) + if err != nil { + return "", fmt.Errorf("invalid host: %w", err) + } + if addr.Is4() || addr.Is4In6() { + return "", errors.New("invalid IPv6 host") + } + return "[" + unescapedHostname + "]" + unescapedColonPort, nil } else if i := strings.LastIndex(host, ":"); i != -1 { colonPort := host[i:] if !validOptionalPort(colonPort) { diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go index 16e08b63c6d098..32065583f27dd7 100644 --- a/src/net/url/url_test.go +++ b/src/net/url/url_test.go @@ -383,6 +383,16 @@ var urltests = []URLTest{ }, "", }, + // valid IPv6 host with port and path + { + "https://[2001:db8::1]:8443/test/path", + &URL{ + Scheme: "https", + Host: "[2001:db8::1]:8443", + Path: "/test/path", + }, + "", + }, // host subcomponent; IPv6 address with zone identifier in RFC 6874 { "http://[fe80::1%25en0]/", // alphanum zone identifier @@ -707,6 +717,24 @@ var parseRequestURLTests = []struct { // RFC 6874. {"http://[fe80::1%en0]/", false}, {"http://[fe80::1%en0]:8080/", false}, + + // Tests exercising RFC 3986 compliance + {"https://[1:2:3:4:5:6:7:8]", true}, // full IPv6 address + {"https://[2001:db8::a:b:c:d]", true}, // compressed IPv6 address + {"https://[fe80::1%25eth0]", true}, // link-local address with zone ID (interface name) + {"https://[fe80::abc:def%254]", true}, // link-local address with zone ID (interface index) + {"https://[2001:db8::1]/path", true}, // compressed IPv6 address with path + {"https://[fe80::1%25eth0]/path?query=1", true}, // link-local with zone, path, and query + + {"https://[::ffff:192.0.2.1]", false}, + {"https://[:1] ", false}, + {"https://[1:2:3:4:5:6:7:8:9]", false}, + {"https://[1::1::1]", false}, + {"https://[1:2:3:]", false}, + {"https://[ffff::127.0.0.4000]", false}, + {"https://[0:0::test.com]:80", false}, + {"https://[2001:db8::test.com]", false}, + {"https://[test.com]", false}, } func TestParseRequestURI(t *testing.T) { @@ -1643,6 +1671,17 @@ func TestParseErrors(t *testing.T) { {"cache_object:foo", true}, {"cache_object:foo/bar", true}, {"cache_object/:foo/bar", false}, + + {"http://[192.168.0.1]/", true}, // IPv4 in brackets + {"http://[192.168.0.1]:8080/", true}, // IPv4 in brackets with port + {"http://[::ffff:192.168.0.1]/", true}, // IPv4-mapped IPv6 in brackets + {"http://[::ffff:192.168.0.1]:8080/", true}, // IPv4-mapped IPv6 in brackets with port + {"http://[::ffff:c0a8:1]/", true}, // IPv4-mapped IPv6 in brackets (hex) + {"http://[not-an-ip]/", true}, // invalid IP string in brackets + {"http://[fe80::1%foo]/", true}, // invalid zone format in brackets + {"http://[fe80::1", true}, // missing closing bracket + {"http://fe80::1]/", true}, // missing opening bracket + {"http://[test.com]/", true}, // domain name in brackets } for _, tt := range tests { u, err := Parse(tt.in) From f0c69db15aae2eb10bddd8b6745dff5c2932e8f5 Mon Sep 17 00:00:00 2001 From: Neal Patel Date: Mon, 15 Sep 2025 16:31:22 -0400 Subject: [PATCH 34/59] [release-branch.go1.25] crypto/x509: improve domain name verification Don't use domainToReverseLabels to check if domain names are valid, since it is not particularly performant, and can contribute to DoS vectors. Instead just iterate over the name and enforce the properties we care about. This also enforces that DNS names, both in SANs and name constraints, are valid. We previously allowed invalid SANs, because some intermediates had these weird names (see #23995), but there are currently no trusted intermediates that have this property, and since we target the web PKI, supporting this particular case is not a high priority. Thank you to Jakub Ciolek for reporting this issue. Fixes CVE-2025-58187 For #75681 Fixes #75715 Change-Id: I6ebce847dcbe5fc63ef2f9a74f53f11c4c56d3d1 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2820 Reviewed-by: Damien Neil Reviewed-by: Roland Shoemaker Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2981 Commit-Queue: Roland Shoemaker Reviewed-by: Nicholas Husin Reviewed-on: https://go-review.googlesource.com/c/go/+/709848 Auto-Submit: Michael Pratt TryBot-Bypass: Michael Pratt Reviewed-by: Carlos Amedee --- src/crypto/x509/name_constraints_test.go | 75 ++--------------------- src/crypto/x509/parser.go | 77 ++++++++++++++---------- src/crypto/x509/parser_test.go | 43 +++++++++++++ src/crypto/x509/verify.go | 1 + 4 files changed, 96 insertions(+), 100 deletions(-) diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go index a5851845164d10..831fcbc8d2eb82 100644 --- a/src/crypto/x509/name_constraints_test.go +++ b/src/crypto/x509/name_constraints_test.go @@ -1456,63 +1456,7 @@ var nameConstraintsTests = []nameConstraintsTest{ expectedError: "incompatible key usage", }, - // An invalid DNS SAN should be detected only at validation time so - // that we can process CA certificates in the wild that have invalid SANs. - // See https://github.com/golang/go/issues/23995 - - // #77: an invalid DNS or mail SAN will not be detected if name constraint - // checking is not triggered. - { - roots: make([]constraintsSpec, 1), - intermediates: [][]constraintsSpec{ - { - {}, - }, - }, - leaf: leafSpec{ - sans: []string{"dns:this is invalid", "email:this @ is invalid"}, - }, - }, - - // #78: an invalid DNS SAN will be detected if any name constraint checking - // is triggered. - { - roots: []constraintsSpec{ - { - bad: []string{"uri:"}, - }, - }, - intermediates: [][]constraintsSpec{ - { - {}, - }, - }, - leaf: leafSpec{ - sans: []string{"dns:this is invalid"}, - }, - expectedError: "cannot parse dnsName", - }, - - // #79: an invalid email SAN will be detected if any name constraint - // checking is triggered. - { - roots: []constraintsSpec{ - { - bad: []string{"uri:"}, - }, - }, - intermediates: [][]constraintsSpec{ - { - {}, - }, - }, - leaf: leafSpec{ - sans: []string{"email:this @ is invalid"}, - }, - expectedError: "cannot parse rfc822Name", - }, - - // #80: if several EKUs are requested, satisfying any of them is sufficient. + // #77: if several EKUs are requested, satisfying any of them is sufficient. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ @@ -1527,7 +1471,7 @@ var nameConstraintsTests = []nameConstraintsTest{ requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth, ExtKeyUsageEmailProtection}, }, - // #81: EKUs that are not asserted in VerifyOpts are not required to be + // #78: EKUs that are not asserted in VerifyOpts are not required to be // nested. { roots: make([]constraintsSpec, 1), @@ -1546,7 +1490,7 @@ var nameConstraintsTests = []nameConstraintsTest{ }, }, - // #82: a certificate without SANs and CN is accepted in a constrained chain. + // #79: a certificate without SANs and CN is accepted in a constrained chain. { roots: []constraintsSpec{ { @@ -1563,7 +1507,7 @@ var nameConstraintsTests = []nameConstraintsTest{ }, }, - // #83: a certificate without SANs and with a CN that does not parse as a + // #80: a certificate without SANs and with a CN that does not parse as a // hostname is accepted in a constrained chain. { roots: []constraintsSpec{ @@ -1582,7 +1526,7 @@ var nameConstraintsTests = []nameConstraintsTest{ }, }, - // #84: a certificate with SANs and CN is accepted in a constrained chain. + // #81: a certificate with SANs and CN is accepted in a constrained chain. { roots: []constraintsSpec{ { @@ -1600,14 +1544,7 @@ var nameConstraintsTests = []nameConstraintsTest{ }, }, - // #85: .example.com is an invalid DNS name, it should not match the - // constraint example.com. - { - roots: []constraintsSpec{{ok: []string{"dns:example.com"}}}, - leaf: leafSpec{sans: []string{"dns:.example.com"}}, - expectedError: "cannot parse dnsName \".example.com\"", - }, - // #86: URIs with IPv6 addresses with zones and ports are rejected + // #82: URIs with IPv6 addresses with zones and ports are rejected { roots: []constraintsSpec{ { diff --git a/src/crypto/x509/parser.go b/src/crypto/x509/parser.go index 4abcc1b7b590e2..9d6bfd6e95f949 100644 --- a/src/crypto/x509/parser.go +++ b/src/crypto/x509/parser.go @@ -413,10 +413,14 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string if err := isIA5String(email); err != nil { return errors.New("x509: SAN rfc822Name is malformed") } + parsed, ok := parseRFC2821Mailbox(email) + if !ok || (ok && !domainNameValid(parsed.domain, false)) { + return errors.New("x509: SAN rfc822Name is malformed") + } emailAddresses = append(emailAddresses, email) case nameTypeDNS: name := string(data) - if err := isIA5String(name); err != nil { + if err := isIA5String(name); err != nil || (err == nil && !domainNameValid(name, false)) { return errors.New("x509: SAN dNSName is malformed") } dnsNames = append(dnsNames, string(name)) @@ -426,14 +430,9 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string return errors.New("x509: SAN uniformResourceIdentifier is malformed") } uri, err := url.Parse(uriStr) - if err != nil { + if err != nil || (err == nil && uri.Host != "" && !domainNameValid(uri.Host, false)) { return fmt.Errorf("x509: cannot parse URI %q: %s", uriStr, err) } - if len(uri.Host) > 0 { - if _, ok := domainToReverseLabels(uri.Host); !ok { - return fmt.Errorf("x509: cannot parse URI %q: invalid domain", uriStr) - } - } uris = append(uris, uri) case nameTypeIP: switch len(data) { @@ -598,15 +597,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error()) } - trimmedDomain := domain - if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' { - // constraints can have a leading - // period to exclude the domain - // itself, but that's not valid in a - // normal domain name. - trimmedDomain = trimmedDomain[1:] - } - if _, ok := domainToReverseLabels(trimmedDomain); !ok { + if !domainNameValid(domain, true) { return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain) } dnsNames = append(dnsNames, domain) @@ -647,12 +638,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint) } } else { - // Otherwise it's a domain name. - domain := constraint - if len(domain) > 0 && domain[0] == '.' { - domain = domain[1:] - } - if _, ok := domainToReverseLabels(domain); !ok { + if !domainNameValid(constraint, true) { return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint) } } @@ -668,15 +654,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", domain) } - trimmedDomain := domain - if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' { - // constraints can have a leading - // period to exclude the domain itself, - // but that's not valid in a normal - // domain name. - trimmedDomain = trimmedDomain[1:] - } - if _, ok := domainToReverseLabels(trimmedDomain); !ok { + if !domainNameValid(domain, true) { return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain) } uriDomains = append(uriDomains, domain) @@ -1317,3 +1295,40 @@ func ParseRevocationList(der []byte) (*RevocationList, error) { return rl, nil } + +// domainNameValid does minimal domain name validity checking. In particular it +// enforces the following properties: +// - names cannot have the trailing period +// - names can only have a leading period if constraint is true +// - names must be <= 253 characters +// - names cannot have empty labels +// - names cannot labels that are longer than 63 characters +// +// Note that this does not enforce the LDH requirements for domain names. +func domainNameValid(s string, constraint bool) bool { + if len(s) == 0 && constraint { + return true + } + if len(s) == 0 || (!constraint && s[0] == '.') || s[len(s)-1] == '.' || len(s) > 253 { + return false + } + lastDot := -1 + if constraint && s[0] == '.' { + s = s[1:] + } + + for i := 0; i <= len(s); i++ { + if i == len(s) || s[i] == '.' { + labelLen := i + if lastDot >= 0 { + labelLen -= lastDot + 1 + } + if labelLen == 0 || labelLen > 63 { + return false + } + lastDot = i + } + } + + return true +} diff --git a/src/crypto/x509/parser_test.go b/src/crypto/x509/parser_test.go index 3b9d9aed826b01..1b553e362e48a0 100644 --- a/src/crypto/x509/parser_test.go +++ b/src/crypto/x509/parser_test.go @@ -8,6 +8,7 @@ import ( "encoding/asn1" "encoding/pem" "os" + "strings" "testing" cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" @@ -251,3 +252,45 @@ d5l1tRhScKu2NBgm74nYmJxJYgvuTA38wGhRrGU= } } } + +func TestDomainNameValid(t *testing.T) { + for _, tc := range []struct { + name string + dnsName string + constraint bool + valid bool + }{ + {"empty name, name", "", false, false}, + {"empty name, constraint", "", true, true}, + {"empty label, name", "a..a", false, false}, + {"empty label, constraint", "a..a", true, false}, + {"period, name", ".", false, false}, + {"period, constraint", ".", true, false}, // TODO(roland): not entirely clear if this is a valid constraint (require at least one label?) + {"valid, name", "a.b.c", false, true}, + {"valid, constraint", "a.b.c", true, true}, + {"leading period, name", ".a.b.c", false, false}, + {"leading period, constraint", ".a.b.c", true, true}, + {"trailing period, name", "a.", false, false}, + {"trailing period, constraint", "a.", true, false}, + {"bare label, name", "a", false, true}, + {"bare label, constraint", "a", true, true}, + {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, false}, + {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, false}, + {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, false}, + {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, false}, + {"64 char single label, name", strings.Repeat("a", 64), false, false}, + {"64 char single label, constraint", strings.Repeat("a", 64), true, false}, + {"63 char single label, name", strings.Repeat("a", 63), false, true}, + {"63 char single label, constraint", strings.Repeat("a", 63), true, true}, + {"64 char label, name", "a." + strings.Repeat("a", 64), false, false}, + {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, false}, + {"63 char label, name", "a." + strings.Repeat("a", 63), false, true}, + {"63 char label, constraint", "a." + strings.Repeat("a", 63), true, true}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.valid != domainNameValid(tc.dnsName, tc.constraint) { + t.Errorf("domainNameValid(%q, %t) = %v; want %v", tc.dnsName, tc.constraint, !tc.valid, tc.valid) + } + }) + } +} diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index 755c1db96c1edb..058153fbe73461 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -391,6 +391,7 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) { // domainToReverseLabels converts a textual domain name like foo.example.com to // the list of labels in reverse order, e.g. ["com", "example", "foo"]. func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { + reverseLabels = make([]string, 0, strings.Count(domain, ".")+1) for len(domain) > 0 { if i := strings.LastIndexByte(domain, '.'); i == -1 { reverseLabels = append(reverseLabels, domain) From 100c5a66802b5a895b1d0e5ed3b7918f899c4833 Mon Sep 17 00:00:00 2001 From: Nicholas Husin Date: Tue, 30 Sep 2025 14:02:38 -0400 Subject: [PATCH 35/59] [release-branch.go1.25] net/http: add httpcookiemaxnum GODEBUG option to limit number of cookies parsed When handling HTTP headers, net/http does not currently limit the number of cookies that can be parsed. The only limitation that exists is for the size of the entire HTTP header, which is controlled by MaxHeaderBytes (defaults to 1 MB). Unfortunately, this allows a malicious actor to send HTTP headers which contain a massive amount of small cookies, such that as much cookies as possible can be fitted within the MaxHeaderBytes limitation. Internally, this causes us to allocate a massive number of Cookie struct. For example, a 1 MB HTTP header with cookies that repeats "a=;" will cause an allocation of ~66 MB in the heap. This can serve as a way for malicious actors to induce memory exhaustion. To fix this, we will now limit the number of cookies we are willing to parse to 3000 by default. This behavior can be changed by setting a new GODEBUG option: GODEBUG=httpcookiemaxnum. httpcookiemaxnum can be set to allow a higher or lower cookie limit. Setting it to 0 will also allow an infinite number of cookies to be parsed. Thanks to jub0bs for reporting this issue. For #75672 Fixes #75707 Fixes CVE-2025-58186 Change-Id: Ied58b3bc8acf5d11c880f881f36ecbf1d5d52622 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2720 Reviewed-by: Roland Shoemaker Reviewed-by: Damien Neil Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2965 Reviewed-by: Nicholas Husin Commit-Queue: Roland Shoemaker Reviewed-on: https://go-review.googlesource.com/c/go/+/709849 TryBot-Bypass: Michael Pratt Reviewed-by: Carlos Amedee Auto-Submit: Michael Pratt --- doc/godebug.md | 10 ++ src/internal/godebugs/table.go | 1 + src/net/http/cookie.go | 59 +++++++++- src/net/http/cookie_test.go | 206 ++++++++++++++++++++++----------- src/runtime/metrics/doc.go | 5 + 5 files changed, 206 insertions(+), 75 deletions(-) diff --git a/doc/godebug.md b/doc/godebug.md index aaa0f9dd55e570..c12ce5311d90d1 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -153,6 +153,16 @@ for example, see the [runtime documentation](/pkg/runtime#hdr-Environment_Variables) and the [go command documentation](/cmd/go#hdr-Build_and_test_caching). +### Go 1.26 + +Go 1.26 added a new `httpcookiemaxnum` setting that controls the maximum number +of cookies that net/http will accept when parsing HTTP headers. If the number of +cookie in a header exceeds the number set in `httpcookiemaxnum`, cookie parsing +will fail early. The default value is `httpcookiemaxnum=3000`. Setting +`httpcookiemaxnum=0` will allow the cookie parsing to accept an indefinite +number of cookies. To avoid denial of service attacks, this setting and default +was backported to Go 1.25.2 and Go 1.24.8. + ### Go 1.25 Go 1.25 added a new `decoratemappings` setting that controls whether the Go diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index 2d008825459bb2..852305e8553aab 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -42,6 +42,7 @@ var All = []Info{ {Name: "http2client", Package: "net/http"}, {Name: "http2debug", Package: "net/http", Opaque: true}, {Name: "http2server", Package: "net/http"}, + {Name: "httpcookiemaxnum", Package: "net/http", Changed: 24, Old: "0"}, {Name: "httplaxcontentlength", Package: "net/http", Changed: 22, Old: "1"}, {Name: "httpmuxgo121", Package: "net/http", Changed: 22, Old: "1"}, {Name: "httpservecontentkeepheaders", Package: "net/http", Changed: 23, Old: "1"}, diff --git a/src/net/http/cookie.go b/src/net/http/cookie.go index 408fe88452b37a..d3c5c168ef21f1 100644 --- a/src/net/http/cookie.go +++ b/src/net/http/cookie.go @@ -7,6 +7,7 @@ package http import ( "errors" "fmt" + "internal/godebug" "log" "net" "net/http/internal/ascii" @@ -16,6 +17,8 @@ import ( "time" ) +var httpcookiemaxnum = godebug.New("httpcookiemaxnum") + // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an // HTTP response or the Cookie header of an HTTP request. // @@ -58,16 +61,37 @@ const ( ) var ( - errBlankCookie = errors.New("http: blank cookie") - errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie") - errInvalidCookieName = errors.New("http: invalid cookie name") - errInvalidCookieValue = errors.New("http: invalid cookie value") + errBlankCookie = errors.New("http: blank cookie") + errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie") + errInvalidCookieName = errors.New("http: invalid cookie name") + errInvalidCookieValue = errors.New("http: invalid cookie value") + errCookieNumLimitExceeded = errors.New("http: number of cookies exceeded limit") ) +const defaultCookieMaxNum = 3000 + +func cookieNumWithinMax(cookieNum int) bool { + withinDefaultMax := cookieNum <= defaultCookieMaxNum + if httpcookiemaxnum.Value() == "" { + return withinDefaultMax + } + if customMax, err := strconv.Atoi(httpcookiemaxnum.Value()); err == nil { + withinCustomMax := customMax == 0 || cookieNum <= customMax + if withinDefaultMax != withinCustomMax { + httpcookiemaxnum.IncNonDefault() + } + return withinCustomMax + } + return withinDefaultMax +} + // ParseCookie parses a Cookie header value and returns all the cookies // which were set in it. Since the same cookie name can appear multiple times // the returned Values can contain more than one value for a given key. func ParseCookie(line string) ([]*Cookie, error) { + if !cookieNumWithinMax(strings.Count(line, ";") + 1) { + return nil, errCookieNumLimitExceeded + } parts := strings.Split(textproto.TrimString(line), ";") if len(parts) == 1 && parts[0] == "" { return nil, errBlankCookie @@ -197,11 +221,21 @@ func ParseSetCookie(line string) (*Cookie, error) { // readSetCookies parses all "Set-Cookie" values from // the header h and returns the successfully parsed Cookies. +// +// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum +// GODEBUG option is not explicitly turned off, this function will silently +// fail and return an empty slice. func readSetCookies(h Header) []*Cookie { cookieCount := len(h["Set-Cookie"]) if cookieCount == 0 { return []*Cookie{} } + // Cookie limit was unfortunately introduced at a later point in time. + // As such, we can only fail by returning an empty slice rather than + // explicit error. + if !cookieNumWithinMax(cookieCount) { + return []*Cookie{} + } cookies := make([]*Cookie, 0, cookieCount) for _, line := range h["Set-Cookie"] { if cookie, err := ParseSetCookie(line); err == nil { @@ -329,13 +363,28 @@ func (c *Cookie) Valid() error { // readCookies parses all "Cookie" values from the header h and // returns the successfully parsed Cookies. // -// if filter isn't empty, only cookies of that name are returned. +// If filter isn't empty, only cookies of that name are returned. +// +// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum +// GODEBUG option is not explicitly turned off, this function will silently +// fail and return an empty slice. func readCookies(h Header, filter string) []*Cookie { lines := h["Cookie"] if len(lines) == 0 { return []*Cookie{} } + // Cookie limit was unfortunately introduced at a later point in time. + // As such, we can only fail by returning an empty slice rather than + // explicit error. + cookieCount := 0 + for _, line := range lines { + cookieCount += strings.Count(line, ";") + 1 + } + if !cookieNumWithinMax(cookieCount) { + return []*Cookie{} + } + cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";")) for _, line := range lines { line = textproto.TrimString(line) diff --git a/src/net/http/cookie_test.go b/src/net/http/cookie_test.go index aac69563624fcd..d028725f31c4a3 100644 --- a/src/net/http/cookie_test.go +++ b/src/net/http/cookie_test.go @@ -11,6 +11,7 @@ import ( "log" "os" "reflect" + "slices" "strings" "testing" "time" @@ -255,16 +256,17 @@ func TestAddCookie(t *testing.T) { } var readSetCookiesTests = []struct { - Header Header - Cookies []*Cookie + header Header + cookies []*Cookie + godebug string }{ { - Header{"Set-Cookie": {"Cookie-1=v$1"}}, - []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}}, + header: Header{"Set-Cookie": {"Cookie-1=v$1"}}, + cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}}, }, { - Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}}, - []*Cookie{{ + header: Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}}, + cookies: []*Cookie{{ Name: "NID", Value: "99=YsDT5i3E-CXax-", Path: "/", @@ -276,8 +278,8 @@ var readSetCookiesTests = []struct { }}, }, { - Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, - []*Cookie{{ + header: Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, + cookies: []*Cookie{{ Name: ".ASPXAUTH", Value: "7E3AA", Path: "/", @@ -288,8 +290,8 @@ var readSetCookiesTests = []struct { }}, }, { - Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}}, - []*Cookie{{ + header: Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}}, + cookies: []*Cookie{{ Name: "ASP.NET_SessionId", Value: "foo", Path: "/", @@ -298,8 +300,8 @@ var readSetCookiesTests = []struct { }}, }, { - Header{"Set-Cookie": {"samesitedefault=foo; SameSite"}}, - []*Cookie{{ + header: Header{"Set-Cookie": {"samesitedefault=foo; SameSite"}}, + cookies: []*Cookie{{ Name: "samesitedefault", Value: "foo", SameSite: SameSiteDefaultMode, @@ -307,8 +309,8 @@ var readSetCookiesTests = []struct { }}, }, { - Header{"Set-Cookie": {"samesiteinvalidisdefault=foo; SameSite=invalid"}}, - []*Cookie{{ + header: Header{"Set-Cookie": {"samesiteinvalidisdefault=foo; SameSite=invalid"}}, + cookies: []*Cookie{{ Name: "samesiteinvalidisdefault", Value: "foo", SameSite: SameSiteDefaultMode, @@ -316,8 +318,8 @@ var readSetCookiesTests = []struct { }}, }, { - Header{"Set-Cookie": {"samesitelax=foo; SameSite=Lax"}}, - []*Cookie{{ + header: Header{"Set-Cookie": {"samesitelax=foo; SameSite=Lax"}}, + cookies: []*Cookie{{ Name: "samesitelax", Value: "foo", SameSite: SameSiteLaxMode, @@ -325,8 +327,8 @@ var readSetCookiesTests = []struct { }}, }, { - Header{"Set-Cookie": {"samesitestrict=foo; SameSite=Strict"}}, - []*Cookie{{ + header: Header{"Set-Cookie": {"samesitestrict=foo; SameSite=Strict"}}, + cookies: []*Cookie{{ Name: "samesitestrict", Value: "foo", SameSite: SameSiteStrictMode, @@ -334,8 +336,8 @@ var readSetCookiesTests = []struct { }}, }, { - Header{"Set-Cookie": {"samesitenone=foo; SameSite=None"}}, - []*Cookie{{ + header: Header{"Set-Cookie": {"samesitenone=foo; SameSite=None"}}, + cookies: []*Cookie{{ Name: "samesitenone", Value: "foo", SameSite: SameSiteNoneMode, @@ -345,47 +347,66 @@ var readSetCookiesTests = []struct { // Make sure we can properly read back the Set-Cookie headers we create // for values containing spaces or commas: { - Header{"Set-Cookie": {`special-1=a z`}}, - []*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}}, + header: Header{"Set-Cookie": {`special-1=a z`}}, + cookies: []*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}}, }, { - Header{"Set-Cookie": {`special-2=" z"`}}, - []*Cookie{{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`}}, + header: Header{"Set-Cookie": {`special-2=" z"`}}, + cookies: []*Cookie{{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`}}, }, { - Header{"Set-Cookie": {`special-3="a "`}}, - []*Cookie{{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`}}, + header: Header{"Set-Cookie": {`special-3="a "`}}, + cookies: []*Cookie{{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`}}, }, { - Header{"Set-Cookie": {`special-4=" "`}}, - []*Cookie{{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`}}, + header: Header{"Set-Cookie": {`special-4=" "`}}, + cookies: []*Cookie{{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`}}, }, { - Header{"Set-Cookie": {`special-5=a,z`}}, - []*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}}, + header: Header{"Set-Cookie": {`special-5=a,z`}}, + cookies: []*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}}, }, { - Header{"Set-Cookie": {`special-6=",z"`}}, - []*Cookie{{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`}}, + header: Header{"Set-Cookie": {`special-6=",z"`}}, + cookies: []*Cookie{{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`}}, }, { - Header{"Set-Cookie": {`special-7=a,`}}, - []*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}}, + header: Header{"Set-Cookie": {`special-7=a,`}}, + cookies: []*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}}, }, { - Header{"Set-Cookie": {`special-8=","`}}, - []*Cookie{{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`}}, + header: Header{"Set-Cookie": {`special-8=","`}}, + cookies: []*Cookie{{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`}}, }, // Make sure we can properly read back the Set-Cookie headers // for names containing spaces: { - Header{"Set-Cookie": {`special-9 =","`}}, - []*Cookie{{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`}}, + header: Header{"Set-Cookie": {`special-9 =","`}}, + cookies: []*Cookie{{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`}}, }, // Quoted values (issue #46443) { - Header{"Set-Cookie": {`cookie="quoted"`}}, - []*Cookie{{Name: "cookie", Value: "quoted", Quoted: true, Raw: `cookie="quoted"`}}, + header: Header{"Set-Cookie": {`cookie="quoted"`}}, + cookies: []*Cookie{{Name: "cookie", Value: "quoted", Quoted: true, Raw: `cookie="quoted"`}}, + }, + { + header: Header{"Set-Cookie": slices.Repeat([]string{"a="}, defaultCookieMaxNum+1)}, + cookies: []*Cookie{}, + }, + { + header: Header{"Set-Cookie": slices.Repeat([]string{"a="}, 10)}, + cookies: []*Cookie{}, + godebug: "httpcookiemaxnum=5", + }, + { + header: Header{"Set-Cookie": strings.Split(strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], ";")}, + cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false, Raw: "a="}}, defaultCookieMaxNum+1), + godebug: "httpcookiemaxnum=0", + }, + { + header: Header{"Set-Cookie": strings.Split(strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], ";")}, + cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false, Raw: "a="}}, defaultCookieMaxNum+1), + godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1), }, // TODO(bradfitz): users have reported seeing this in the @@ -405,79 +426,103 @@ func toJSON(v any) string { func TestReadSetCookies(t *testing.T) { for i, tt := range readSetCookiesTests { + t.Setenv("GODEBUG", tt.godebug) for n := 0; n < 2; n++ { // to verify readSetCookies doesn't mutate its input - c := readSetCookies(tt.Header) - if !reflect.DeepEqual(c, tt.Cookies) { - t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.Cookies)) + c := readSetCookies(tt.header) + if !reflect.DeepEqual(c, tt.cookies) { + t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.cookies)) } } } } var readCookiesTests = []struct { - Header Header - Filter string - Cookies []*Cookie + header Header + filter string + cookies []*Cookie + godebug string }{ { - Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, - "", - []*Cookie{ + header: Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, + filter: "", + cookies: []*Cookie{ {Name: "Cookie-1", Value: "v$1"}, {Name: "c2", Value: "v2"}, }, }, { - Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, - "c2", - []*Cookie{ + header: Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, + filter: "c2", + cookies: []*Cookie{ {Name: "c2", Value: "v2"}, }, }, { - Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, - "", - []*Cookie{ + header: Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, + filter: "", + cookies: []*Cookie{ {Name: "Cookie-1", Value: "v$1"}, {Name: "c2", Value: "v2"}, }, }, { - Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, - "c2", - []*Cookie{ + header: Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, + filter: "c2", + cookies: []*Cookie{ {Name: "c2", Value: "v2"}, }, }, { - Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}}, - "", - []*Cookie{ + header: Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}}, + filter: "", + cookies: []*Cookie{ {Name: "Cookie-1", Value: "v$1", Quoted: true}, {Name: "c2", Value: "v2", Quoted: true}, }, }, { - Header{"Cookie": {`Cookie-1="v$1"; c2=v2;`}}, - "", - []*Cookie{ + header: Header{"Cookie": {`Cookie-1="v$1"; c2=v2;`}}, + filter: "", + cookies: []*Cookie{ {Name: "Cookie-1", Value: "v$1", Quoted: true}, {Name: "c2", Value: "v2"}, }, }, { - Header{"Cookie": {``}}, - "", - []*Cookie{}, + header: Header{"Cookie": {``}}, + filter: "", + cookies: []*Cookie{}, + }, + // GODEBUG=httpcookiemaxnum should work regardless if all cookies are sent + // via one "Cookie" field, or multiple fields. + { + header: Header{"Cookie": {strings.Repeat(";a=", defaultCookieMaxNum+1)[1:]}}, + cookies: []*Cookie{}, + }, + { + header: Header{"Cookie": slices.Repeat([]string{"a="}, 10)}, + cookies: []*Cookie{}, + godebug: "httpcookiemaxnum=5", + }, + { + header: Header{"Cookie": {strings.Repeat(";a=", defaultCookieMaxNum+1)[1:]}}, + cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1), + godebug: "httpcookiemaxnum=0", + }, + { + header: Header{"Cookie": slices.Repeat([]string{"a="}, defaultCookieMaxNum+1)}, + cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1), + godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1), }, } func TestReadCookies(t *testing.T) { for i, tt := range readCookiesTests { + t.Setenv("GODEBUG", tt.godebug) for n := 0; n < 2; n++ { // to verify readCookies doesn't mutate its input - c := readCookies(tt.Header, tt.Filter) - if !reflect.DeepEqual(c, tt.Cookies) { - t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.Cookies)) + c := readCookies(tt.header, tt.filter) + if !reflect.DeepEqual(c, tt.cookies) { + t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.cookies)) } } } @@ -689,6 +734,7 @@ func TestParseCookie(t *testing.T) { line string cookies []*Cookie err error + godebug string }{ { line: "Cookie-1=v$1", @@ -722,8 +768,28 @@ func TestParseCookie(t *testing.T) { line: "k1=\\", err: errInvalidCookieValue, }, + { + line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], + err: errCookieNumLimitExceeded, + }, + { + line: strings.Repeat(";a=", 10)[1:], + err: errCookieNumLimitExceeded, + godebug: "httpcookiemaxnum=5", + }, + { + line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], + cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1), + godebug: "httpcookiemaxnum=0", + }, + { + line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], + cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1), + godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1), + }, } for i, tt := range tests { + t.Setenv("GODEBUG", tt.godebug) gotCookies, gotErr := ParseCookie(tt.line) if !errors.Is(gotErr, tt.err) { t.Errorf("#%d ParseCookie got error %v, want error %v", i, gotErr, tt.err) diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index a1902bc6d78c7f..251ee22fa1a51b 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -282,6 +282,11 @@ Below is the full list of supported metrics, ordered lexicographically. The number of non-default behaviors executed by the net/http package due to a non-default GODEBUG=http2server=... setting. + /godebug/non-default-behavior/httpcookiemaxnum:events + The number of non-default behaviors executed by the net/http + package due to a non-default GODEBUG=httpcookiemaxnum=... + setting. + /godebug/non-default-behavior/httplaxcontentlength:events The number of non-default behaviors executed by the net/http package due to a non-default GODEBUG=httplaxcontentlength=... From e0f655bf3f96410f90756f49532bc6a1851855ca Mon Sep 17 00:00:00 2001 From: Nicholas Husin Date: Wed, 3 Sep 2025 09:30:56 -0400 Subject: [PATCH 36/59] [release-branch.go1.25] encoding/asn1: prevent memory exhaustion when parsing using internal/saferio Within parseSequenceOf, reflect.MakeSlice is being used to pre-allocate a slice that is needed in order to fully validate the given DER payload. The size of the slice allocated are also multiple times larger than the input DER: - When using asn1.Unmarshal directly, the allocated slice is ~28x larger. - When passing in DER using x509.ParseCertificateRequest, the allocated slice is ~48x larger. - When passing in DER using ocsp.ParseResponse, the allocated slice is ~137x larger. As a result, a malicious actor can craft a big empty DER payload, resulting in an unnecessary large allocation of memories. This can be a way to cause memory exhaustion. To prevent this, we now use SliceCapWithSize within internal/saferio to enforce a memory allocation cap. Thanks to Jakub Ciolek for reporting this issue. For #75671 Fixes #75705 Fixes CVE-2025-58185 Change-Id: Id50e76187eda43f594be75e516b9ca1d2ae6f428 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2700 Reviewed-by: Roland Shoemaker Reviewed-by: Damien Neil Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2966 Reviewed-by: Nicholas Husin Commit-Queue: Roland Shoemaker Reviewed-on: https://go-review.googlesource.com/c/go/+/709850 TryBot-Bypass: Michael Pratt Reviewed-by: Carlos Amedee Auto-Submit: Michael Pratt --- src/encoding/asn1/asn1.go | 10 ++++++++- src/encoding/asn1/asn1_test.go | 38 ++++++++++++++++++++++++++++++++++ src/go/build/deps_test.go | 2 +- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/encoding/asn1/asn1.go b/src/encoding/asn1/asn1.go index 0b64f06d368b53..f4be515b98ef1c 100644 --- a/src/encoding/asn1/asn1.go +++ b/src/encoding/asn1/asn1.go @@ -22,6 +22,7 @@ package asn1 import ( "errors" "fmt" + "internal/saferio" "math" "math/big" "reflect" @@ -666,10 +667,17 @@ func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type offset += t.length numElements++ } - ret = reflect.MakeSlice(sliceType, numElements, numElements) + elemSize := uint64(elemType.Size()) + safeCap := saferio.SliceCapWithSize(elemSize, uint64(numElements)) + if safeCap < 0 { + err = SyntaxError{fmt.Sprintf("%s slice too big: %d elements of %d bytes", elemType.Kind(), numElements, elemSize)} + return + } + ret = reflect.MakeSlice(sliceType, 0, safeCap) params := fieldParameters{} offset := 0 for i := 0; i < numElements; i++ { + ret = reflect.Append(ret, reflect.Zero(elemType)) offset, err = parseField(ret.Index(i), bytes, offset, params) if err != nil { return diff --git a/src/encoding/asn1/asn1_test.go b/src/encoding/asn1/asn1_test.go index 0597740bd5e5ec..41cc0ba50ec304 100644 --- a/src/encoding/asn1/asn1_test.go +++ b/src/encoding/asn1/asn1_test.go @@ -7,10 +7,12 @@ package asn1 import ( "bytes" "encoding/hex" + "errors" "fmt" "math" "math/big" "reflect" + "runtime" "strings" "testing" "time" @@ -1216,3 +1218,39 @@ func TestImplicitTypeRoundtrip(t *testing.T) { t.Fatalf("Unexpected diff after roundtripping struct\na: %#v\nb: %#v", a, b) } } + +func TestParsingMemoryConsumption(t *testing.T) { + // Craft a syntatically valid, but empty, ~10 MB DER bomb. A successful + // unmarshal of this bomb should yield ~280 MB. However, the parsing should + // fail due to the empty content; and, in such cases, we want to make sure + // that we do not unnecessarily allocate memories. + derBomb := make([]byte, 10_000_000) + for i := range derBomb { + derBomb[i] = 0x30 + } + derBomb = append([]byte{0x30, 0x83, 0x98, 0x96, 0x80}, derBomb...) + + var m runtime.MemStats + runtime.GC() + runtime.ReadMemStats(&m) + memBefore := m.TotalAlloc + + var out []struct { + Id []int + Critical bool `asn1:"optional"` + Value []byte + } + _, err := Unmarshal(derBomb, &out) + if !errors.As(err, &SyntaxError{}) { + t.Fatalf("Incorrect error result: want (%v), but got (%v) instead", &SyntaxError{}, err) + } + + runtime.ReadMemStats(&m) + memDiff := m.TotalAlloc - memBefore + + // Ensure that the memory allocated does not exceed 10<<21 (~20 MB) when + // the parsing fails. + if memDiff > 10<<21 { + t.Errorf("Too much memory allocated while parsing DER: %v MiB", memDiff/1024/1024) + } +} diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index b3ca2a017edd26..641d1a325a5c07 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -559,7 +559,7 @@ var depsRules = ` # CRYPTO-MATH is crypto that exposes math/big APIs - no cgo, net; fmt now ok. - CRYPTO, FMT, math/big + CRYPTO, FMT, math/big, internal/saferio < crypto/internal/boring/bbig < crypto/internal/fips140cache < crypto/rand From 90f72bd5001d0278949fab0b7a40f7d8c712979b Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Tue, 30 Sep 2025 11:16:56 -0700 Subject: [PATCH 37/59] [release-branch.go1.25] encoding/pem: make Decode complexity linear Because Decode scanned the input first for the first BEGIN line, and then the first END line, the complexity of Decode is quadratic. If the input contained a large number of BEGINs and then a single END right at the end of the input, we would find the first BEGIN, and then scan the entire input for the END, and fail to parse the block, so move onto the next BEGIN, scan the entire input for the END, etc. Instead, look for the first END in the input, and then the first BEGIN that precedes the found END. We then process the bytes between the BEGIN and END, and move onto the bytes after the END for further processing. This gives us linear complexity. Fixes CVE-2025-61723 For #75676 Fixes #75709 Change-Id: I813c4f63e78bca4054226c53e13865c781564ccf Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2921 Reviewed-by: Nicholas Husin Reviewed-by: Damien Neil Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2985 Reviewed-on: https://go-review.googlesource.com/c/go/+/709851 Reviewed-by: Carlos Amedee Auto-Submit: Michael Pratt TryBot-Bypass: Michael Pratt --- src/encoding/pem/pem.go | 67 ++++++++++++++++++++---------------- src/encoding/pem/pem_test.go | 13 +++---- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/encoding/pem/pem.go b/src/encoding/pem/pem.go index dcc7416ee21ffe..21887008ca2182 100644 --- a/src/encoding/pem/pem.go +++ b/src/encoding/pem/pem.go @@ -37,7 +37,7 @@ type Block struct { // line bytes. The remainder of the byte array (also not including the new line // bytes) is also returned and this will always be smaller than the original // argument. -func getLine(data []byte) (line, rest []byte) { +func getLine(data []byte) (line, rest []byte, consumed int) { i := bytes.IndexByte(data, '\n') var j int if i < 0 { @@ -49,7 +49,7 @@ func getLine(data []byte) (line, rest []byte) { i-- } } - return bytes.TrimRight(data[0:i], " \t"), data[j:] + return bytes.TrimRight(data[0:i], " \t"), data[j:], j } // removeSpacesAndTabs returns a copy of its input with all spaces and tabs @@ -90,20 +90,32 @@ func Decode(data []byte) (p *Block, rest []byte) { // pemStart begins with a newline. However, at the very beginning of // the byte array, we'll accept the start string without it. rest = data + for { - if bytes.HasPrefix(rest, pemStart[1:]) { - rest = rest[len(pemStart)-1:] - } else if _, after, ok := bytes.Cut(rest, pemStart); ok { - rest = after - } else { + // Find the first END line, and then find the last BEGIN line before + // the end line. This lets us skip any repeated BEGIN lines that don't + // have a matching END. + endIndex := bytes.Index(rest, pemEnd) + if endIndex < 0 { + return nil, data + } + endTrailerIndex := endIndex + len(pemEnd) + beginIndex := bytes.LastIndex(rest[:endIndex], pemStart[1:]) + if beginIndex < 0 || beginIndex > 0 && rest[beginIndex-1] != '\n' { return nil, data } + rest = rest[beginIndex+len(pemStart)-1:] + endIndex -= beginIndex + len(pemStart) - 1 + endTrailerIndex -= beginIndex + len(pemStart) - 1 var typeLine []byte - typeLine, rest = getLine(rest) + var consumed int + typeLine, rest, consumed = getLine(rest) if !bytes.HasSuffix(typeLine, pemEndOfLine) { continue } + endIndex -= consumed + endTrailerIndex -= consumed typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)] p = &Block{ @@ -117,7 +129,7 @@ func Decode(data []byte) (p *Block, rest []byte) { if len(rest) == 0 { return nil, data } - line, next := getLine(rest) + line, next, consumed := getLine(rest) key, val, ok := bytes.Cut(line, colon) if !ok { @@ -129,21 +141,13 @@ func Decode(data []byte) (p *Block, rest []byte) { val = bytes.TrimSpace(val) p.Headers[string(key)] = string(val) rest = next + endIndex -= consumed + endTrailerIndex -= consumed } - var endIndex, endTrailerIndex int - - // If there were no headers, the END line might occur - // immediately, without a leading newline. - if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) { - endIndex = 0 - endTrailerIndex = len(pemEnd) - 1 - } else { - endIndex = bytes.Index(rest, pemEnd) - endTrailerIndex = endIndex + len(pemEnd) - } - - if endIndex < 0 { + // If there were headers, there must be a newline between the headers + // and the END line, so endIndex should be >= 0. + if len(p.Headers) > 0 && endIndex < 0 { continue } @@ -163,21 +167,24 @@ func Decode(data []byte) (p *Block, rest []byte) { } // The line must end with only whitespace. - if s, _ := getLine(restOfEndLine); len(s) != 0 { + if s, _, _ := getLine(restOfEndLine); len(s) != 0 { continue } - base64Data := removeSpacesAndTabs(rest[:endIndex]) - p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data))) - n, err := base64.StdEncoding.Decode(p.Bytes, base64Data) - if err != nil { - continue + p.Bytes = []byte{} + if endIndex > 0 { + base64Data := removeSpacesAndTabs(rest[:endIndex]) + p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data))) + n, err := base64.StdEncoding.Decode(p.Bytes, base64Data) + if err != nil { + continue + } + p.Bytes = p.Bytes[:n] } - p.Bytes = p.Bytes[:n] // the -1 is because we might have only matched pemEnd without the // leading newline if the PEM block was empty. - _, rest = getLine(rest[endIndex+len(pemEnd)-1:]) + _, rest, _ = getLine(rest[endIndex+len(pemEnd)-1:]) return p, rest } } diff --git a/src/encoding/pem/pem_test.go b/src/encoding/pem/pem_test.go index e252ffd8ed1613..2c9b3eabcd1c12 100644 --- a/src/encoding/pem/pem_test.go +++ b/src/encoding/pem/pem_test.go @@ -34,7 +34,7 @@ var getLineTests = []GetLineTest{ func TestGetLine(t *testing.T) { for i, test := range getLineTests { - x, y := getLine([]byte(test.in)) + x, y, _ := getLine([]byte(test.in)) if string(x) != test.out1 || string(y) != test.out2 { t.Errorf("#%d got:%+v,%+v want:%s,%s", i, x, y, test.out1, test.out2) } @@ -46,6 +46,7 @@ func TestDecode(t *testing.T) { if !reflect.DeepEqual(result, certificate) { t.Errorf("#0 got:%#v want:%#v", result, certificate) } + result, remainder = Decode(remainder) if !reflect.DeepEqual(result, privateKey) { t.Errorf("#1 got:%#v want:%#v", result, privateKey) @@ -68,7 +69,7 @@ func TestDecode(t *testing.T) { } result, remainder = Decode(remainder) - if result == nil || result.Type != "HEADERS" || len(result.Headers) != 1 { + if result == nil || result.Type != "VALID HEADERS" || len(result.Headers) != 1 { t.Errorf("#5 expected single header block but got :%v", result) } @@ -381,15 +382,15 @@ ZWAaUoVtWIQ52aKS0p19G99hhb+IVANC4akkdHV4SP8i7MVNZhfUmg== # This shouldn't be recognised because of the missing newline after the headers. ------BEGIN HEADERS----- +-----BEGIN INVALID HEADERS----- Header: 1 ------END HEADERS----- +-----END INVALID HEADERS----- # This should be valid, however. ------BEGIN HEADERS----- +-----BEGIN VALID HEADERS----- Header: 1 ------END HEADERS-----`) +-----END VALID HEADERS-----`) var certificate = &Block{Type: "CERTIFICATE", Headers: map[string]string{}, From 2612dcfd3cb6dd73c76e14a24fe1a68e2708e4e3 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Thu, 11 Sep 2025 13:32:10 -0700 Subject: [PATCH 38/59] [release-branch.go1.25] archive/tar: set a limit on the size of GNU sparse file 1.0 regions Sparse files in tar archives contain only the non-zero components of the file. There are several different encodings for sparse files. When reading GNU tar pax 1.0 sparse files, archive/tar did not set a limit on the size of the sparse region data. A malicious archive containing a large number of sparse blocks could cause archive/tar to read an unbounded amount of data from the archive into memory. Since a malicious input can be highly compressable, a small compressed input could cause very large allocations. Cap the size of the sparse block data to the same limit used for PAX headers (1 MiB). Thanks to Harshit Gupta (Mr HAX) (https://www.linkedin.com/in/iam-harshit-gupta/) for reporting this issue. Fixes CVE-2025-58183 For #75677 Fixes #75711 Change-Id: I70b907b584a7b8676df8a149a1db728ae681a770 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2800 Reviewed-by: Roland Shoemaker Reviewed-by: Nicholas Husin Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2987 Reviewed-by: Damien Neil Reviewed-on: https://go-review.googlesource.com/c/go/+/709852 TryBot-Bypass: Michael Pratt Reviewed-by: Carlos Amedee Auto-Submit: Michael Pratt --- src/archive/tar/common.go | 1 + src/archive/tar/reader.go | 9 +++++++-- src/archive/tar/reader_test.go | 5 +++++ .../tar/testdata/gnu-sparse-many-zeros.tar.bz2 | Bin 0 -> 1642 bytes 4 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 src/archive/tar/testdata/gnu-sparse-many-zeros.tar.bz2 diff --git a/src/archive/tar/common.go b/src/archive/tar/common.go index 7b3945ff153144..ad31bbb64aaa5c 100644 --- a/src/archive/tar/common.go +++ b/src/archive/tar/common.go @@ -39,6 +39,7 @@ var ( errMissData = errors.New("archive/tar: sparse file references non-existent data") errUnrefData = errors.New("archive/tar: sparse file contains unreferenced data") errWriteHole = errors.New("archive/tar: write non-NUL byte in sparse hole") + errSparseTooLong = errors.New("archive/tar: sparse map too long") ) type headerError []string diff --git a/src/archive/tar/reader.go b/src/archive/tar/reader.go index 8483fb52a28f66..16ac2f5b17c28b 100644 --- a/src/archive/tar/reader.go +++ b/src/archive/tar/reader.go @@ -531,12 +531,17 @@ func readGNUSparseMap1x0(r io.Reader) (sparseDatas, error) { cntNewline int64 buf bytes.Buffer blk block + totalSize int ) // feedTokens copies data in blocks from r into buf until there are // at least cnt newlines in buf. It will not read more blocks than needed. feedTokens := func(n int64) error { for cntNewline < n { + totalSize += len(blk) + if totalSize > maxSpecialFileSize { + return errSparseTooLong + } if _, err := mustReadFull(r, blk[:]); err != nil { return err } @@ -569,8 +574,8 @@ func readGNUSparseMap1x0(r io.Reader) (sparseDatas, error) { } // Parse for all member entries. - // numEntries is trusted after this since a potential attacker must have - // committed resources proportional to what this library used. + // numEntries is trusted after this since feedTokens limits the number of + // tokens based on maxSpecialFileSize. if err := feedTokens(2 * numEntries); err != nil { return nil, err } diff --git a/src/archive/tar/reader_test.go b/src/archive/tar/reader_test.go index 99340a30471914..fca53dae741bd5 100644 --- a/src/archive/tar/reader_test.go +++ b/src/archive/tar/reader_test.go @@ -621,6 +621,11 @@ func TestReader(t *testing.T) { }, Format: FormatPAX, }}, + }, { + // Small compressed file that uncompresses to + // a file with a very large GNU 1.0 sparse map. + file: "testdata/gnu-sparse-many-zeros.tar.bz2", + err: errSparseTooLong, }} for _, v := range vectors { diff --git a/src/archive/tar/testdata/gnu-sparse-many-zeros.tar.bz2 b/src/archive/tar/testdata/gnu-sparse-many-zeros.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..751d7fd4b68be1a7439413b4089dbbde33a2900a GIT binary patch literal 1642 zcmZ>Y%CIzaj8qGb%wCwXiS2H0{DC_Y9GL$z2|RUTNGrcrz`)SKVZdrPcO01{ps1pvVeGD$*OEWa_73ksV;zBwoQh0BPEH%vtSITeAlMvtlB3E`^y)8{ zw~&+g8{-?Ahs_oW3u(QcHog6FpP~f~~=z-9g zf_wTZ7^XHZn&-}Zc8R{Fft9C2PEyk*pJkiw?fk^YcK6r>1_p%&1_y>jhFQI$94?0z za?Cl(nljTjt?TS76W-mu3JeU63=9nnhZs#ILKKBvodhkrxK0|)I$ibrgoSju6wr@N z42%qnElgP^KzXhfrD74$NvWo@w9QvsFnedxz`)4Dz{J4J!Ez-rpv4zx#WM|Ul~dWe zujeZ~$i0&Z3?dE&76vu}&J`B}T70wwI?n`zc}}^OvF6@lhub?YF)*+QFmN#NCX1lVw7II6Iq$It1A~AA zg93v=gRdLVZlD!@quw8l{n5lfn)hi|^v!SyiA^y0F{gnCT=X?DusWMCWpyr3vv&I) ZCg2!yghOkJkj3svnul7NKE7EI0RUq%CTjoy literal 0 HcmV?d00001 From bed6c81c2dceeb19c7f9e46fc173671abcda7cfd Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Tue, 7 Oct 2025 11:10:28 -0700 Subject: [PATCH 39/59] [release-branch.go1.25] go1.25.2 Change-Id: I0a685789be057167e6d40fbdaee29ebdbc6a2164 Reviewed-on: https://go-review.googlesource.com/c/go/+/709916 Reviewed-by: Michael Pratt Reviewed-by: Carlos Amedee TryBot-Bypass: Gopher Robot Auto-Submit: Gopher Robot --- VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index cf1f2f4efa54b9..4097adc4304410 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -go1.25.1 -time 2025-08-27T15:49:40Z +go1.25.2 +time 2025-10-02T18:00:14Z From 79ec0c94f32b0fedd0a4e9aacbe0b305b2a66762 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Mon, 6 Oct 2025 10:36:13 -0700 Subject: [PATCH 40/59] [release-branch.go1.25] spec: update spec date to match release date Ths spec date in the 1.25 release branch dates back to Feb 2025 which is rather confusing. Moving it forward to 1.25 release date. This is a roll forward of CL 709515 which was rolled back by CL 709535. For #75743. Fixes #75777. Change-Id: I18d7ccfc343aa1f8fba78a896fb69ad6eeb182e7 Reviewed-on: https://go-review.googlesource.com/c/go/+/710215 Reviewed-by: Dmitri Shuralyov Auto-Submit: Dmitri Shuralyov Reviewed-by: Robert Griesemer Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI --- doc/go_spec.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/go_spec.html b/doc/go_spec.html index 183bc7fb372755..2b47fb2e834d83 100644 --- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -1,6 +1,6 @@ From e05b2c92d973ecf5a8042fd9605501b842e18369 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Thu, 9 Oct 2025 13:35:24 -0700 Subject: [PATCH 41/59] [release-branch.go1.25] crypto/x509: rework fix for CVE-2025-58187 In CL 709854 we enabled strict validation for a number of properties of domain names (and their constraints). This caused significant breakage, since we didn't previously disallow the creation of certificates which contained these malformed domains. Rollback a number of the properties we enforced, making domainNameValid only enforce the same properties that domainToReverseLabels does. Since this also undoes some of the DoS protections our initial fix enabled, this change also adds caching of constraints in isValid (which perhaps is the fix we should've initially chosen). Updates #75835 Updates #75828 Fixes #75861 Change-Id: Ie6ca6b4f30e9b8a143692b64757f7bbf4671ed0e Reviewed-on: https://go-review.googlesource.com/c/go/+/710735 LUCI-TryBot-Result: Go LUCI Reviewed-by: Damien Neil (cherry picked from commit 1cd71689f2ed8f07031a0cc58fc3586ca501839f) Reviewed-on: https://go-review.googlesource.com/c/go/+/710677 Auto-Submit: Michael Pratt Reviewed-by: Michael Pratt --- src/crypto/x509/name_constraints_test.go | 75 +++++++++++++++++++-- src/crypto/x509/parser.go | 57 +++++++++++----- src/crypto/x509/parser_test.go | 84 +++++++++++++++++++++--- src/crypto/x509/verify.go | 53 ++++++++++----- src/crypto/x509/verify_test.go | 2 +- 5 files changed, 221 insertions(+), 50 deletions(-) diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go index 831fcbc8d2eb82..a5851845164d10 100644 --- a/src/crypto/x509/name_constraints_test.go +++ b/src/crypto/x509/name_constraints_test.go @@ -1456,7 +1456,63 @@ var nameConstraintsTests = []nameConstraintsTest{ expectedError: "incompatible key usage", }, - // #77: if several EKUs are requested, satisfying any of them is sufficient. + // An invalid DNS SAN should be detected only at validation time so + // that we can process CA certificates in the wild that have invalid SANs. + // See https://github.com/golang/go/issues/23995 + + // #77: an invalid DNS or mail SAN will not be detected if name constraint + // checking is not triggered. + { + roots: make([]constraintsSpec, 1), + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:this is invalid", "email:this @ is invalid"}, + }, + }, + + // #78: an invalid DNS SAN will be detected if any name constraint checking + // is triggered. + { + roots: []constraintsSpec{ + { + bad: []string{"uri:"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:this is invalid"}, + }, + expectedError: "cannot parse dnsName", + }, + + // #79: an invalid email SAN will be detected if any name constraint + // checking is triggered. + { + roots: []constraintsSpec{ + { + bad: []string{"uri:"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"email:this @ is invalid"}, + }, + expectedError: "cannot parse rfc822Name", + }, + + // #80: if several EKUs are requested, satisfying any of them is sufficient. { roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ @@ -1471,7 +1527,7 @@ var nameConstraintsTests = []nameConstraintsTest{ requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth, ExtKeyUsageEmailProtection}, }, - // #78: EKUs that are not asserted in VerifyOpts are not required to be + // #81: EKUs that are not asserted in VerifyOpts are not required to be // nested. { roots: make([]constraintsSpec, 1), @@ -1490,7 +1546,7 @@ var nameConstraintsTests = []nameConstraintsTest{ }, }, - // #79: a certificate without SANs and CN is accepted in a constrained chain. + // #82: a certificate without SANs and CN is accepted in a constrained chain. { roots: []constraintsSpec{ { @@ -1507,7 +1563,7 @@ var nameConstraintsTests = []nameConstraintsTest{ }, }, - // #80: a certificate without SANs and with a CN that does not parse as a + // #83: a certificate without SANs and with a CN that does not parse as a // hostname is accepted in a constrained chain. { roots: []constraintsSpec{ @@ -1526,7 +1582,7 @@ var nameConstraintsTests = []nameConstraintsTest{ }, }, - // #81: a certificate with SANs and CN is accepted in a constrained chain. + // #84: a certificate with SANs and CN is accepted in a constrained chain. { roots: []constraintsSpec{ { @@ -1544,7 +1600,14 @@ var nameConstraintsTests = []nameConstraintsTest{ }, }, - // #82: URIs with IPv6 addresses with zones and ports are rejected + // #85: .example.com is an invalid DNS name, it should not match the + // constraint example.com. + { + roots: []constraintsSpec{{ok: []string{"dns:example.com"}}}, + leaf: leafSpec{sans: []string{"dns:.example.com"}}, + expectedError: "cannot parse dnsName \".example.com\"", + }, + // #86: URIs with IPv6 addresses with zones and ports are rejected { roots: []constraintsSpec{ { diff --git a/src/crypto/x509/parser.go b/src/crypto/x509/parser.go index 9d6bfd6e95f949..680dcee203a828 100644 --- a/src/crypto/x509/parser.go +++ b/src/crypto/x509/parser.go @@ -413,14 +413,10 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string if err := isIA5String(email); err != nil { return errors.New("x509: SAN rfc822Name is malformed") } - parsed, ok := parseRFC2821Mailbox(email) - if !ok || (ok && !domainNameValid(parsed.domain, false)) { - return errors.New("x509: SAN rfc822Name is malformed") - } emailAddresses = append(emailAddresses, email) case nameTypeDNS: name := string(data) - if err := isIA5String(name); err != nil || (err == nil && !domainNameValid(name, false)) { + if err := isIA5String(name); err != nil { return errors.New("x509: SAN dNSName is malformed") } dnsNames = append(dnsNames, string(name)) @@ -430,9 +426,12 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string return errors.New("x509: SAN uniformResourceIdentifier is malformed") } uri, err := url.Parse(uriStr) - if err != nil || (err == nil && uri.Host != "" && !domainNameValid(uri.Host, false)) { + if err != nil { return fmt.Errorf("x509: cannot parse URI %q: %s", uriStr, err) } + if len(uri.Host) > 0 && !domainNameValid(uri.Host, false) { + return fmt.Errorf("x509: cannot parse URI %q: invalid domain", uriStr) + } uris = append(uris, uri) case nameTypeIP: switch len(data) { @@ -1296,36 +1295,58 @@ func ParseRevocationList(der []byte) (*RevocationList, error) { return rl, nil } -// domainNameValid does minimal domain name validity checking. In particular it -// enforces the following properties: -// - names cannot have the trailing period -// - names can only have a leading period if constraint is true -// - names must be <= 253 characters -// - names cannot have empty labels -// - names cannot labels that are longer than 63 characters -// -// Note that this does not enforce the LDH requirements for domain names. +// domainNameValid is an alloc-less version of the checks that +// domainToReverseLabels does. func domainNameValid(s string, constraint bool) bool { - if len(s) == 0 && constraint { + // TODO(#75835): This function omits a number of checks which we + // really should be doing to enforce that domain names are valid names per + // RFC 1034. We previously enabled these checks, but this broke a + // significant number of certificates we previously considered valid, and we + // happily create via CreateCertificate (et al). We should enable these + // checks, but will need to gate them behind a GODEBUG. + // + // I have left the checks we previously enabled, noted with "TODO(#75835)" so + // that we can easily re-enable them once we unbreak everyone. + + // TODO(#75835): this should only be true for constraints. + if len(s) == 0 { return true } - if len(s) == 0 || (!constraint && s[0] == '.') || s[len(s)-1] == '.' || len(s) > 253 { + + // Do not allow trailing period (FQDN format is not allowed in SANs or + // constraints). + if s[len(s)-1] == '.' { return false } + + // TODO(#75835): domains must have at least one label, cannot have + // a leading empty label, and cannot be longer than 253 characters. + // if len(s) == 0 || (!constraint && s[0] == '.') || len(s) > 253 { + // return false + // } + lastDot := -1 if constraint && s[0] == '.' { s = s[1:] } for i := 0; i <= len(s); i++ { + if i < len(s) && (s[i] < 33 || s[i] > 126) { + // Invalid character. + return false + } if i == len(s) || s[i] == '.' { labelLen := i if lastDot >= 0 { labelLen -= lastDot + 1 } - if labelLen == 0 || labelLen > 63 { + if labelLen == 0 { return false } + // TODO(#75835): labels cannot be longer than 63 characters. + // if labelLen > 63 { + // return false + // } lastDot = i } } diff --git a/src/crypto/x509/parser_test.go b/src/crypto/x509/parser_test.go index 1b553e362e48a0..d53b805b786990 100644 --- a/src/crypto/x509/parser_test.go +++ b/src/crypto/x509/parser_test.go @@ -5,6 +5,9 @@ package x509 import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "encoding/asn1" "encoding/pem" "os" @@ -260,7 +263,31 @@ func TestDomainNameValid(t *testing.T) { constraint bool valid bool }{ - {"empty name, name", "", false, false}, + // TODO(#75835): these tests are for stricter name validation, which we + // had to disable. Once we reenable these strict checks, behind a + // GODEBUG, we should add them back in. + // {"empty name, name", "", false, false}, + // {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, false}, + // {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, false}, + // {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, false}, + // {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, false}, + // {"64 char single label, name", strings.Repeat("a", 64), false, false}, + // {"64 char single label, constraint", strings.Repeat("a", 64), true, false}, + // {"64 char label, name", "a." + strings.Repeat("a", 64), false, false}, + // {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, false}, + + // TODO(#75835): these are the inverse of the tests above, they should be removed + // once the strict checking is enabled. + {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, true}, + {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, true}, + {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, true}, + {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, true}, + {"64 char single label, name", strings.Repeat("a", 64), false, true}, + {"64 char single label, constraint", strings.Repeat("a", 64), true, true}, + {"64 char label, name", "a." + strings.Repeat("a", 64), false, true}, + {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, true}, + + // Check we properly enforce properties of domain names. {"empty name, constraint", "", true, true}, {"empty label, name", "a..a", false, false}, {"empty label, constraint", "a..a", true, false}, @@ -274,23 +301,60 @@ func TestDomainNameValid(t *testing.T) { {"trailing period, constraint", "a.", true, false}, {"bare label, name", "a", false, true}, {"bare label, constraint", "a", true, true}, - {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, false}, - {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, false}, - {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, false}, - {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, false}, - {"64 char single label, name", strings.Repeat("a", 64), false, false}, - {"64 char single label, constraint", strings.Repeat("a", 64), true, false}, {"63 char single label, name", strings.Repeat("a", 63), false, true}, {"63 char single label, constraint", strings.Repeat("a", 63), true, true}, - {"64 char label, name", "a." + strings.Repeat("a", 64), false, false}, - {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, false}, {"63 char label, name", "a." + strings.Repeat("a", 63), false, true}, {"63 char label, constraint", "a." + strings.Repeat("a", 63), true, true}, } { t.Run(tc.name, func(t *testing.T) { - if tc.valid != domainNameValid(tc.dnsName, tc.constraint) { + valid := domainNameValid(tc.dnsName, tc.constraint) + if tc.valid != valid { t.Errorf("domainNameValid(%q, %t) = %v; want %v", tc.dnsName, tc.constraint, !tc.valid, tc.valid) } + // Also check that we enforce the same properties as domainToReverseLabels + trimmedName := tc.dnsName + if tc.constraint && len(trimmedName) > 1 && trimmedName[0] == '.' { + trimmedName = trimmedName[1:] + } + _, revValid := domainToReverseLabels(trimmedName) + if valid != revValid { + t.Errorf("domainNameValid(%q, %t) = %t != domainToReverseLabels(%q) = %t", tc.dnsName, tc.constraint, valid, trimmedName, revValid) + } }) } } + +func TestRoundtripWeirdSANs(t *testing.T) { + // TODO(#75835): check that certificates we create with CreateCertificate that have malformed SAN values + // can be parsed by ParseCertificate. We should eventually restrict this, but for now we have to maintain + // this property as people have been relying on it. + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + badNames := []string{ + "baredomain", + "baredomain.", + strings.Repeat("a", 255), + strings.Repeat("a", 65) + ".com", + } + tmpl := &Certificate{ + EmailAddresses: badNames, + DNSNames: badNames, + } + b, err := CreateCertificate(rand.Reader, tmpl, tmpl, &k.PublicKey, k) + if err != nil { + t.Fatal(err) + } + _, err = ParseCertificate(b) + if err != nil { + t.Fatalf("Couldn't roundtrip certificate: %v", err) + } +} + +func FuzzDomainNameValid(f *testing.F) { + f.Fuzz(func(t *testing.T, data string) { + domainNameValid(data, false) + domainNameValid(data, true) + }) +} diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index 058153fbe73461..bf7e7ec058db2b 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -429,7 +429,7 @@ func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { return reverseLabels, true } -func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, error) { +func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { // If the constraint contains an @, then it specifies an exact mailbox // name. if strings.Contains(constraint, "@") { @@ -442,10 +442,10 @@ func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, erro // Otherwise the constraint is like a DNS constraint of the domain part // of the mailbox. - return matchDomainConstraint(mailbox.domain, constraint) + return matchDomainConstraint(mailbox.domain, constraint, reversedDomainsCache, reversedConstraintsCache) } -func matchURIConstraint(uri *url.URL, constraint string) (bool, error) { +func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { // From RFC 5280, Section 4.2.1.10: // “a uniformResourceIdentifier that does not include an authority // component with a host name specified as a fully qualified domain @@ -474,7 +474,7 @@ func matchURIConstraint(uri *url.URL, constraint string) (bool, error) { return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String()) } - return matchDomainConstraint(host, constraint) + return matchDomainConstraint(host, constraint, reversedDomainsCache, reversedConstraintsCache) } func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { @@ -491,16 +491,21 @@ func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { return true, nil } -func matchDomainConstraint(domain, constraint string) (bool, error) { +func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { // The meaning of zero length constraints is not specified, but this // code follows NSS and accepts them as matching everything. if len(constraint) == 0 { return true, nil } - domainLabels, ok := domainToReverseLabels(domain) - if !ok { - return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain) + domainLabels, found := reversedDomainsCache[domain] + if !found { + var ok bool + domainLabels, ok = domainToReverseLabels(domain) + if !ok { + return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain) + } + reversedDomainsCache[domain] = domainLabels } // RFC 5280 says that a leading period in a domain name means that at @@ -514,9 +519,14 @@ func matchDomainConstraint(domain, constraint string) (bool, error) { constraint = constraint[1:] } - constraintLabels, ok := domainToReverseLabels(constraint) - if !ok { - return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint) + constraintLabels, found := reversedConstraintsCache[constraint] + if !found { + var ok bool + constraintLabels, ok = domainToReverseLabels(constraint) + if !ok { + return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint) + } + reversedConstraintsCache[constraint] = constraintLabels } if len(domainLabels) < len(constraintLabels) || @@ -637,6 +647,19 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V } } + // Each time we do constraint checking, we need to check the constraints in + // the current certificate against all of the names that preceded it. We + // reverse these names using domainToReverseLabels, which is a relatively + // expensive operation. Since we check each name against each constraint, + // this requires us to do N*C calls to domainToReverseLabels (where N is the + // total number of names that preceed the certificate, and C is the total + // number of constraints in the certificate). By caching the results of + // calling domainToReverseLabels, we can reduce that to N+C calls at the + // cost of keeping all of the parsed names and constraints in memory until + // we return from isValid. + reversedDomainsCache := map[string][]string{} + reversedConstraintsCache := map[string][]string{} + if (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints() { toCheck := []*Certificate{} @@ -657,20 +680,20 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox, func(parsedName, constraint any) (bool, error) { - return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string)) + return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), reversedDomainsCache, reversedConstraintsCache) }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil { return err } case nameTypeDNS: name := string(data) - if _, ok := domainToReverseLabels(name); !ok { + if !domainNameValid(name, false) { return fmt.Errorf("x509: cannot parse dnsName %q", name) } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name, func(parsedName, constraint any) (bool, error) { - return matchDomainConstraint(parsedName.(string), constraint.(string)) + return matchDomainConstraint(parsedName.(string), constraint.(string), reversedDomainsCache, reversedConstraintsCache) }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil { return err } @@ -684,7 +707,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri, func(parsedName, constraint any) (bool, error) { - return matchURIConstraint(parsedName.(*url.URL), constraint.(string)) + return matchURIConstraint(parsedName.(*url.URL), constraint.(string), reversedDomainsCache, reversedConstraintsCache) }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil { return err } diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go index 5595f99ea5e43a..60a4cea9146adf 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -1352,7 +1352,7 @@ var nameConstraintTests = []struct { func TestNameConstraints(t *testing.T) { for i, test := range nameConstraintTests { - result, err := matchDomainConstraint(test.domain, test.constraint) + result, err := matchDomainConstraint(test.domain, test.constraint, map[string][]string{}, map[string][]string{}) if err != nil && !test.expectError { t.Errorf("unexpected error for test #%d: domain=%s, constraint=%s, err=%s", i, test.domain, test.constraint, err) From 28622c19591d95c9a83f706f2ed1b303d58da85f Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Mon, 13 Oct 2025 14:05:25 -0700 Subject: [PATCH 42/59] [release-branch.go1.25] go1.25.3 Change-Id: Ibb61bf455e8ec92bb10038b1de0ce79ee771c53a Reviewed-on: https://go-review.googlesource.com/c/go/+/711481 Reviewed-by: Michael Pratt Reviewed-by: Carlos Amedee Auto-Submit: Gopher Robot TryBot-Bypass: Gopher Robot --- VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 4097adc4304410..05d3fa7c5e8101 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -go1.25.2 -time 2025-10-02T18:00:14Z +go1.25.3 +time 2025-10-13T16:08:43Z From f6db7350e1287c1d3570b182756cc75f5c7789fe Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Wed, 8 Oct 2025 17:13:12 -0700 Subject: [PATCH 43/59] [release-branch.go1.25] net/url: allow IP-literals with IPv4-mapped IPv6 addresses The security fix we applied in CL709857 was overly broad. It applied rules from RFC 2732, which disallowed IPv4-mapped IPv6 addresses, but these were later allowed in RFC 3986, which is the canonical URI syntax RFC. Revert the portion of CL709857 which restricted IPv4-mapped addresses, and update the related tests. Updates #75815 Fixes #75832 Change-Id: I3192f2275ad5c386f5c15006a6716bdb5282919d Reviewed-on: https://go-review.googlesource.com/c/go/+/710375 LUCI-TryBot-Result: Go LUCI Reviewed-by: Ethan Lee Auto-Submit: Roland Shoemaker (cherry picked from commit 9db7e30bb42eed9912f5e7e9e3959f3b38879d5b) Reviewed-on: https://go-review.googlesource.com/c/go/+/712240 Reviewed-by: Dmitri Shuralyov Auto-Submit: Dmitri Shuralyov TryBot-Bypass: Dmitri Shuralyov --- src/net/url/url.go | 6 +++--- src/net/url/url_test.go | 23 ++++++++++++----------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/net/url/url.go b/src/net/url/url.go index 40faa7cb9e1c6f..1c50e069613a81 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -673,13 +673,13 @@ func parseHost(host string) (string, error) { // Per RFC 3986, only a host identified by a valid // IPv6 address can be enclosed by square brackets. - // This excludes any IPv4 or IPv4-mapped addresses. + // This excludes any IPv4, but notably not IPv4-mapped addresses. addr, err := netip.ParseAddr(unescapedHostname) if err != nil { return "", fmt.Errorf("invalid host: %w", err) } - if addr.Is4() || addr.Is4In6() { - return "", errors.New("invalid IPv6 host") + if addr.Is4() { + return "", errors.New("invalid IP-literal") } return "[" + unescapedHostname + "]" + unescapedColonPort, nil } else if i := strings.LastIndex(host, ":"); i != -1 { diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go index 32065583f27dd7..6084facacc0519 100644 --- a/src/net/url/url_test.go +++ b/src/net/url/url_test.go @@ -726,7 +726,7 @@ var parseRequestURLTests = []struct { {"https://[2001:db8::1]/path", true}, // compressed IPv6 address with path {"https://[fe80::1%25eth0]/path?query=1", true}, // link-local with zone, path, and query - {"https://[::ffff:192.0.2.1]", false}, + {"https://[::ffff:192.0.2.1]", true}, {"https://[:1] ", false}, {"https://[1:2:3:4:5:6:7:8:9]", false}, {"https://[1::1::1]", false}, @@ -1672,16 +1672,17 @@ func TestParseErrors(t *testing.T) { {"cache_object:foo/bar", true}, {"cache_object/:foo/bar", false}, - {"http://[192.168.0.1]/", true}, // IPv4 in brackets - {"http://[192.168.0.1]:8080/", true}, // IPv4 in brackets with port - {"http://[::ffff:192.168.0.1]/", true}, // IPv4-mapped IPv6 in brackets - {"http://[::ffff:192.168.0.1]:8080/", true}, // IPv4-mapped IPv6 in brackets with port - {"http://[::ffff:c0a8:1]/", true}, // IPv4-mapped IPv6 in brackets (hex) - {"http://[not-an-ip]/", true}, // invalid IP string in brackets - {"http://[fe80::1%foo]/", true}, // invalid zone format in brackets - {"http://[fe80::1", true}, // missing closing bracket - {"http://fe80::1]/", true}, // missing opening bracket - {"http://[test.com]/", true}, // domain name in brackets + {"http://[192.168.0.1]/", true}, // IPv4 in brackets + {"http://[192.168.0.1]:8080/", true}, // IPv4 in brackets with port + {"http://[::ffff:192.168.0.1]/", false}, // IPv4-mapped IPv6 in brackets + {"http://[::ffff:192.168.0.1000]/", true}, // Out of range IPv4-mapped IPv6 in brackets + {"http://[::ffff:192.168.0.1]:8080/", false}, // IPv4-mapped IPv6 in brackets with port + {"http://[::ffff:c0a8:1]/", false}, // IPv4-mapped IPv6 in brackets (hex) + {"http://[not-an-ip]/", true}, // invalid IP string in brackets + {"http://[fe80::1%foo]/", true}, // invalid zone format in brackets + {"http://[fe80::1", true}, // missing closing bracket + {"http://fe80::1]/", true}, // missing opening bracket + {"http://[test.com]/", true}, // domain name in brackets } for _, tt := range tests { u, err := Parse(tt.in) From 7e049e5c3143153fd135864b94b6d417f9e6c2a5 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Wed, 15 Oct 2025 10:45:04 -0700 Subject: [PATCH 44/59] [release-branch.go1.25] encoding/pem: properly decode strange PEM data When the passed byte slice has leading garbage, properly handle ignoring it and continuing to parse the slice until we find a valid block (or nothing). Fixes #75952 Change-Id: I07e937d9c754fd71b028b99450b48f57b4464457 Reviewed-on: https://go-review.googlesource.com/c/go/+/712140 Reviewed-by: Damien Neil LUCI-TryBot-Result: Go LUCI (cherry picked from commit 09830901714d8b3a2cc5fb33e87a81886b21ea24) Reviewed-on: https://go-review.googlesource.com/c/go/+/712640 Reviewed-by: Dmitri Shuralyov --- src/encoding/pem/pem.go | 11 ++-- src/encoding/pem/pem_test.go | 97 ++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/src/encoding/pem/pem.go b/src/encoding/pem/pem.go index 21887008ca2182..2356096ade24d2 100644 --- a/src/encoding/pem/pem.go +++ b/src/encoding/pem/pem.go @@ -91,7 +91,12 @@ func Decode(data []byte) (p *Block, rest []byte) { // the byte array, we'll accept the start string without it. rest = data + endTrailerIndex := 0 for { + // If we've already tried parsing a block, skip past the END we already + // saw. + rest = rest[endTrailerIndex:] + // Find the first END line, and then find the last BEGIN line before // the end line. This lets us skip any repeated BEGIN lines that don't // have a matching END. @@ -99,10 +104,10 @@ func Decode(data []byte) (p *Block, rest []byte) { if endIndex < 0 { return nil, data } - endTrailerIndex := endIndex + len(pemEnd) + endTrailerIndex = endIndex + len(pemEnd) beginIndex := bytes.LastIndex(rest[:endIndex], pemStart[1:]) - if beginIndex < 0 || beginIndex > 0 && rest[beginIndex-1] != '\n' { - return nil, data + if beginIndex < 0 || (beginIndex > 0 && rest[beginIndex-1] != '\n') { + continue } rest = rest[beginIndex+len(pemStart)-1:] endIndex -= beginIndex + len(pemStart) - 1 diff --git a/src/encoding/pem/pem_test.go b/src/encoding/pem/pem_test.go index 2c9b3eabcd1c12..5bdc2f66a7b3ab 100644 --- a/src/encoding/pem/pem_test.go +++ b/src/encoding/pem/pem_test.go @@ -639,3 +639,100 @@ func TestBadEncode(t *testing.T) { } func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } + +func TestDecodeStrangeCases(t *testing.T) { + sentinelType := "TEST BLOCK" + sentinelBytes := []byte("hello") + for _, tc := range []struct { + name string + pem string + }{ + { + name: "invalid section (not base64)", + pem: `-----BEGIN COMMENT----- +foo foo foo +-----END COMMENT----- +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + { + name: "leading garbage on block", + pem: `foo foo foo-----BEGIN CERTIFICATE----- +MCowBQYDK2VwAyEApVjJeLW5MoP6uR3+OeITokM+rBDng6dgl1vvhcy+wws= +-----END PUBLIC KEY----- +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + { + name: "leading garbage", + pem: `foo foo foo +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + { + name: "leading partial block", + pem: `foo foo foo +-----END COMMENT----- +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + { + name: "multiple BEGIN", + pem: `-----BEGIN TEST BLOCK----- +-----BEGIN TEST BLOCK----- +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + { + name: "multiple END", + pem: `-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK----- +-----END TEST BLOCK----- +-----END TEST BLOCK-----`, + }, + { + name: "leading malformed BEGIN", + pem: `-----BEGIN PUBLIC KEY +aGVsbG8= +-----END PUBLIC KEY----- +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + block, _ := Decode([]byte(tc.pem)) + if block == nil { + t.Fatal("expected valid block") + } + if block.Type != sentinelType { + t.Fatalf("unexpected block returned, got type %q, want type %q", block.Type, sentinelType) + } + if !bytes.Equal(block.Bytes, sentinelBytes) { + t.Fatalf("unexpected block content, got %x, want %x", block.Bytes, sentinelBytes) + } + }) + } +} + +func TestJustEnd(t *testing.T) { + pemData := ` +-----END PUBLIC KEY-----` + + block, _ := Decode([]byte(pemData)) + if block != nil { + t.Fatal("unexpected block") + } +} + +func FuzzDecode(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + Decode(data) + }) +} From bbb76271230a801e085bb813e82fd40000b65367 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 15 Sep 2025 17:31:46 +0700 Subject: [PATCH 45/59] [release-branch.go1.25] cmd/compile: prevent shapifying of pointer shape type CL 641955 changes the Unified IR reader to not doing shapify when reading reshaping expression, prevent losing of the original type. This is an oversight, as the main problem isn't about shaping during the reshaping process itself, but about the specific case of shaping a pointer shape type. This bug occurs when instantiating a generic function within another generic function with a pointer shape type as type parameter, which will convert `*[]go.shape.T` to `*go.shape.uint8`, resulting in the loss of the original expression's type. This commit changes Unified IR reader to avoid pointer shaping for `*[]go.shape.T`, ensures that the original type is preserved when processing reshaping expressions. Fixes #75480 Change-Id: Icede6b73247d0d367bb485619f2dafb60ad66806 Reviewed-on: https://go-review.googlesource.com/c/go/+/704095 Auto-Submit: Cuong Manh Le LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase Reviewed-by: Junyang Shao Reviewed-on: https://go-review.googlesource.com/c/go/+/706216 Reviewed-by: Ed Schouten Reviewed-by: Cherry Mui --- src/cmd/compile/internal/noder/reader.go | 46 ++++------- .../compile/testdata/script/issue75461.txt | 78 +++++++++++++++++++ 2 files changed, 91 insertions(+), 33 deletions(-) create mode 100644 src/cmd/compile/testdata/script/issue75461.txt diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go index 38b0bc1d8a4153..7256801965cc44 100644 --- a/src/cmd/compile/internal/noder/reader.go +++ b/src/cmd/compile/internal/noder/reader.go @@ -49,9 +49,6 @@ type pkgReader struct { // but bitwise inverted so we can detect if we're missing the entry // or not. newindex []index - - // indicates whether the data is reading during reshaping. - reshaping bool } func newPkgReader(pr pkgbits.PkgDecoder) *pkgReader { @@ -119,10 +116,6 @@ type reader struct { // find parameters/results. funarghack bool - // reshaping is used during reading exprReshape code, preventing - // the reader from shapifying the re-shaped type. - reshaping bool - // methodSym is the name of method's name, if reading a method. // It's nil if reading a normal function or closure body. methodSym *types.Sym @@ -937,8 +930,19 @@ func shapify(targ *types.Type, basic bool) *types.Type { // types, and discarding struct field names and tags. However, we'll // need to start tracking how type parameters are actually used to // implement some of these optimizations. + pointerShaping := basic && targ.IsPtr() && !targ.Elem().NotInHeap() + // The exception is when the type parameter is a pointer to a type + // which `Type.HasShape()` returns true, but `Type.IsShape()` returns + // false, like `*[]go.shape.T`. This is because the type parameter is + // used to instantiate a generic function inside another generic function. + // In this case, we want to keep the targ as-is, otherwise, we may lose the + // original type after `*[]go.shape.T` is shapified to `*go.shape.uint8`. + // See issue #54535, #71184. + if pointerShaping && !targ.Elem().IsShape() && targ.Elem().HasShape() { + return targ + } under := targ.Underlying() - if basic && targ.IsPtr() && !targ.Elem().NotInHeap() { + if pointerShaping { under = types.NewPtr(types.Types[types.TUINT8]) } @@ -1014,25 +1018,7 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx index, implicits, explicits // arguments. for i, targ := range dict.targs { basic := r.Bool() - isPointerShape := basic && targ.IsPtr() && !targ.Elem().NotInHeap() - // We should not do shapify during the reshaping process, see #71184. - // However, this only matters for shapify a pointer type, which will - // lose the original underlying type. - // - // Example with a pointer type: - // - // - First, shapifying *[]T -> *uint8 - // - During the reshaping process, *uint8 is shapified to *go.shape.uint8 - // - This ends up with a different type with the original *[]T - // - // For a non-pointer type: - // - // - int -> go.shape.int - // - go.shape.int -> go.shape.int - // - // We always end up with the identical type. - canShapify := !pr.reshaping || !isPointerShape - if dict.shaped && canShapify { + if dict.shaped { dict.targs[i] = shapify(targ, basic) } } @@ -2470,10 +2456,7 @@ func (r *reader) expr() (res ir.Node) { case exprReshape: typ := r.typ() - old := r.reshaping - r.reshaping = true x := r.expr() - r.reshaping = old if types.IdenticalStrict(x.Type(), typ) { return x @@ -2596,10 +2579,7 @@ func (r *reader) funcInst(pos src.XPos) (wrapperFn, baseFn, dictPtr ir.Node) { info := r.dict.subdicts[idx] explicits := r.p.typListIdx(info.explicits, r.dict) - old := r.p.reshaping - r.p.reshaping = r.reshaping baseFn = r.p.objIdx(info.idx, implicits, explicits, true).(*ir.Name) - r.p.reshaping = old // TODO(mdempsky): Is there a more robust way to get the // dictionary pointer type here? diff --git a/src/cmd/compile/testdata/script/issue75461.txt b/src/cmd/compile/testdata/script/issue75461.txt new file mode 100644 index 00000000000000..05f0fd4cfae304 --- /dev/null +++ b/src/cmd/compile/testdata/script/issue75461.txt @@ -0,0 +1,78 @@ +go build main.go +! stdout . +! stderr . + +-- main.go -- +package main + +import ( + "demo/registry" +) + +func main() { + _ = registry.NewUserRegistry() +} + +-- go.mod -- +module demo + +go 1.24 + +-- model/user.go -- +package model + +type User struct { + ID int +} + +func (c *User) String() string { + return "" +} + +-- ordered/map.go -- +package ordered + +type OrderedMap[K comparable, V any] struct { + m map[K]V +} + +func New[K comparable, V any](options ...any) *OrderedMap[K, V] { + orderedMap := &OrderedMap[K, V]{} + return orderedMap +} + +-- registry/user.go -- +package registry + +import ( + "demo/model" + "demo/ordered" +) + +type baseRegistry = Registry[model.User, *model.User] + +type UserRegistry struct { + *baseRegistry +} + +type Registry[T any, P PStringer[T]] struct { + m *ordered.OrderedMap[string, P] +} + +type PStringer[T any] interface { + *T + String() string +} + +func NewRegistry[T any, P PStringer[T]]() *Registry[T, P] { + r := &Registry[T, P]{ + m: ordered.New[string, P](), + } + return r +} + +func NewUserRegistry() *UserRegistry { + return &UserRegistry{ + baseRegistry: NewRegistry[model.User](), + } +} From bf95b767394eb5643265f44c7b98bdbb85b897ce Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 2 Sep 2025 15:46:11 -0700 Subject: [PATCH 46/59] [release-branch.go1.25] runtime: use one more address bit for tagged pointers We use one extra bit to placate systems which simulate amd64 binaries on an arm64 host. Allocated arm64 addresses could be as high as 1<<48-1, which would be invalid if we assumed 48-bit sign-extended addresses. (Note that this does not help the other way around, simluating arm64 on amd64, but we don't have that problem at the moment.) For #69255. Fixes #75775. Change-Id: Iace17a5d41a65e34abf201d03d8b0ff6f7bf1150 Reviewed-on: https://go-review.googlesource.com/c/go/+/700515 Reviewed-by: Keith Randall Auto-Submit: Keith Randall LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek (cherry picked from commit 2a7f1d47b0650c92b47f0cd5bc3536d438e4bbbe) Reviewed-on: https://go-review.googlesource.com/c/go/+/712800 Reviewed-by: Keith Randall Reviewed-by: David Chase --- src/runtime/tagptr_64bit.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/runtime/tagptr_64bit.go b/src/runtime/tagptr_64bit.go index 3d79332e2dcaff..76733cc1d64630 100644 --- a/src/runtime/tagptr_64bit.go +++ b/src/runtime/tagptr_64bit.go @@ -22,10 +22,17 @@ const ( // On AMD64, virtual addresses are 48-bit (or 57-bit) sign-extended. // Other archs are 48-bit zero-extended. // + // We use one extra bit to placate systems which simulate amd64 binaries on + // an arm64 host. Allocated arm64 addresses could be as high as 1<<48-1, + // which would be invalid if we assumed 48-bit sign-extended addresses. + // See issue 69255. + // (Note that this does not help the other way around, simluating arm64 + // on amd64, but we don't have that problem at the moment.) + // // On s390x, virtual addresses are 64-bit. There's not much we // can do about this, so we just hope that the kernel doesn't // get to really high addresses and panic if it does. - defaultAddrBits = 48 + defaultAddrBits = 48 + 1 // On AIX, 64-bit addresses are split into 36-bit segment number and 28-bit // offset in segment. Segment numbers in the range 0x0A0000000-0x0AFFFFFFF(LSA) From cd21a7b31b5191f9d6c776ef7a552375cd12aed9 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 7 Oct 2025 10:12:43 -0700 Subject: [PATCH 47/59] [release-branch.go1.25] Revert "crypto/internal/fips140/subtle: add assembly implementation of xorBytes for mipsx" This reverts commit 343e486bfdbf9ca614d3e197afd79ad7ed5fef3e. Reason for revert: doesn't handle unaligned accesses correctly. Update #75790 Change-Id: I1d6210eeca9336f2ce311e99944cb270565563aa Reviewed-on: https://go-review.googlesource.com/c/go/+/709795 Reviewed-by: Cherry Mui Reviewed-by: Michael Knyszek LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall (cherry picked from commit cb81270113968408d7cc41c0b1530adb51dd8496) Reviewed-on: https://go-review.googlesource.com/c/go/+/709758 Reviewed-by: David Chase --- src/crypto/internal/fips140/subtle/xor_asm.go | 2 +- .../internal/fips140/subtle/xor_generic.go | 2 +- .../internal/fips140/subtle/xor_mipsx.s | 212 ------------------ 3 files changed, 2 insertions(+), 214 deletions(-) delete mode 100644 src/crypto/internal/fips140/subtle/xor_mipsx.s diff --git a/src/crypto/internal/fips140/subtle/xor_asm.go b/src/crypto/internal/fips140/subtle/xor_asm.go index b07239da3e31c1..e10ea8b441429b 100644 --- a/src/crypto/internal/fips140/subtle/xor_asm.go +++ b/src/crypto/internal/fips140/subtle/xor_asm.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (amd64 || arm64 || mips || mipsle || mips64 || mips64le || ppc64 || ppc64le || riscv64) && !purego +//go:build (amd64 || arm64 || mips64 || mips64le || ppc64 || ppc64le || riscv64) && !purego package subtle diff --git a/src/crypto/internal/fips140/subtle/xor_generic.go b/src/crypto/internal/fips140/subtle/xor_generic.go index ed484bc630e98d..08af84de2a3dab 100644 --- a/src/crypto/internal/fips140/subtle/xor_generic.go +++ b/src/crypto/internal/fips140/subtle/xor_generic.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (!amd64 && !arm64 && !loong64 && !mips && !mipsle && !mips64 && !mips64le && !ppc64 && !ppc64le && !riscv64) || purego +//go:build (!amd64 && !arm64 && !loong64 && !mips64 && !mips64le && !ppc64 && !ppc64le && !riscv64) || purego package subtle diff --git a/src/crypto/internal/fips140/subtle/xor_mipsx.s b/src/crypto/internal/fips140/subtle/xor_mipsx.s deleted file mode 100644 index 1a6b3f409dddc9..00000000000000 --- a/src/crypto/internal/fips140/subtle/xor_mipsx.s +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2025 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 (mips || mipsle) && !purego - -#include "textflag.h" - -// func xorBytes(dst, a, b *byte, n int) -TEXT ·xorBytes(SB), NOSPLIT|NOFRAME, $0 - MOVW dst+0(FP), R1 - MOVW a+4(FP), R2 - MOVW b+8(FP), R3 - MOVW n+12(FP), R4 - - SGTU $64, R4, R5 // R5 = 1 if (64 > R4) - BNE R5, xor_32_check -xor_64: - MOVW (R2), R6 - MOVW 4(R2), R7 - MOVW 8(R2), R8 - MOVW 12(R2), R9 - MOVW (R3), R10 - MOVW 4(R3), R11 - MOVW 8(R3), R12 - MOVW 12(R3), R13 - XOR R6, R10 - XOR R7, R11 - XOR R8, R12 - XOR R9, R13 - MOVW R10, (R1) - MOVW R11, 4(R1) - MOVW R12, 8(R1) - MOVW R13, 12(R1) - MOVW 16(R2), R6 - MOVW 20(R2), R7 - MOVW 24(R2), R8 - MOVW 28(R2), R9 - MOVW 16(R3), R10 - MOVW 20(R3), R11 - MOVW 24(R3), R12 - MOVW 28(R3), R13 - XOR R6, R10 - XOR R7, R11 - XOR R8, R12 - XOR R9, R13 - MOVW R10, 16(R1) - MOVW R11, 20(R1) - MOVW R12, 24(R1) - MOVW R13, 28(R1) - MOVW 32(R2), R6 - MOVW 36(R2), R7 - MOVW 40(R2), R8 - MOVW 44(R2), R9 - MOVW 32(R3), R10 - MOVW 36(R3), R11 - MOVW 40(R3), R12 - MOVW 44(R3), R13 - XOR R6, R10 - XOR R7, R11 - XOR R8, R12 - XOR R9, R13 - MOVW R10, 32(R1) - MOVW R11, 36(R1) - MOVW R12, 40(R1) - MOVW R13, 44(R1) - MOVW 48(R2), R6 - MOVW 52(R2), R7 - MOVW 56(R2), R8 - MOVW 60(R2), R9 - MOVW 48(R3), R10 - MOVW 52(R3), R11 - MOVW 56(R3), R12 - MOVW 60(R3), R13 - XOR R6, R10 - XOR R7, R11 - XOR R8, R12 - XOR R9, R13 - MOVW R10, 48(R1) - MOVW R11, 52(R1) - MOVW R12, 56(R1) - MOVW R13, 60(R1) - ADD $64, R2 - ADD $64, R3 - ADD $64, R1 - SUB $64, R4 - SGTU $64, R4, R5 - BEQ R0, R5, xor_64 - BEQ R0, R4, end - -xor_32_check: - SGTU $32, R4, R5 - BNE R5, xor_16_check -xor_32: - MOVW (R2), R6 - MOVW 4(R2), R7 - MOVW 8(R2), R8 - MOVW 12(R2), R9 - MOVW (R3), R10 - MOVW 4(R3), R11 - MOVW 8(R3), R12 - MOVW 12(R3), R13 - XOR R6, R10 - XOR R7, R11 - XOR R8, R12 - XOR R9, R13 - MOVW R10, (R1) - MOVW R11, 4(R1) - MOVW R12, 8(R1) - MOVW R13, 12(R1) - MOVW 16(R2), R6 - MOVW 20(R2), R7 - MOVW 24(R2), R8 - MOVW 28(R2), R9 - MOVW 16(R3), R10 - MOVW 20(R3), R11 - MOVW 24(R3), R12 - MOVW 28(R3), R13 - XOR R6, R10 - XOR R7, R11 - XOR R8, R12 - XOR R9, R13 - MOVW R10, 16(R1) - MOVW R11, 20(R1) - MOVW R12, 24(R1) - MOVW R13, 28(R1) - ADD $32, R2 - ADD $32, R3 - ADD $32, R1 - SUB $32, R4 - BEQ R0, R4, end - -xor_16_check: - SGTU $16, R4, R5 - BNE R5, xor_8_check -xor_16: - MOVW (R2), R6 - MOVW 4(R2), R7 - MOVW 8(R2), R8 - MOVW 12(R2), R9 - MOVW (R3), R10 - MOVW 4(R3), R11 - MOVW 8(R3), R12 - MOVW 12(R3), R13 - XOR R6, R10 - XOR R7, R11 - XOR R8, R12 - XOR R9, R13 - MOVW R10, (R1) - MOVW R11, 4(R1) - MOVW R12, 8(R1) - MOVW R13, 12(R1) - ADD $16, R2 - ADD $16, R3 - ADD $16, R1 - SUB $16, R4 - BEQ R0, R4, end - -xor_8_check: - SGTU $8, R4, R5 - BNE R5, xor_4_check -xor_8: - MOVW (R2), R6 - MOVW 4(R2), R7 - MOVW (R3), R8 - MOVW 4(R3), R9 - XOR R6, R8 - XOR R7, R9 - MOVW R8, (R1) - MOVW R9, 4(R1) - ADD $8, R1 - ADD $8, R2 - ADD $8, R3 - SUB $8, R4 - BEQ R0, R4, end - -xor_4_check: - SGTU $4, R4, R5 - BNE R5, xor_2_check -xor_4: - MOVW (R2), R6 - MOVW (R3), R7 - XOR R6, R7 - MOVW R7, (R1) - ADD $4, R2 - ADD $4, R3 - ADD $4, R1 - SUB $4, R4 - BEQ R0, R4, end - -xor_2_check: - SGTU $2, R4, R5 - BNE R5, xor_1 -xor_2: - MOVH (R2), R6 - MOVH (R3), R7 - XOR R6, R7 - MOVH R7, (R1) - ADD $2, R2 - ADD $2, R3 - ADD $2, R1 - SUB $2, R4 - BEQ R0, R4, end - -xor_1: - MOVB (R2), R6 - MOVB (R3), R7 - XOR R6, R7 - MOVB R7, (R1) - -end: - RET From 4942c74d04295c72e293b4a67200513b9a36f99d Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 7 Oct 2025 10:15:43 -0700 Subject: [PATCH 48/59] [release-branch.go1.25] Revert "crypto/internal/fips140/subtle: add assembly implementation of xorBytes for mips64x" This reverts commit 49d6777d87a0abb3eda032da95eff024156835f7. Reason for revert: doesn't handle unaligned accesses correctly Fixes #75790 Change-Id: Ia272245a6a2a91b305d411207430bad660ee355b Reviewed-on: https://go-review.googlesource.com/c/go/+/709757 Reviewed-by: Keith Randall Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI (cherry picked from commit a1661e776f57602b4d4470389a0246f9784fd722) Reviewed-on: https://go-review.googlesource.com/c/go/+/709798 Reviewed-by: Keith Randall Reviewed-by: David Chase Reviewed-by: Michael Knyszek --- src/crypto/internal/fips140/subtle/xor_asm.go | 2 +- .../internal/fips140/subtle/xor_generic.go | 2 +- .../internal/fips140/subtle/xor_mips64x.s | 153 ------------------ 3 files changed, 2 insertions(+), 155 deletions(-) delete mode 100644 src/crypto/internal/fips140/subtle/xor_mips64x.s diff --git a/src/crypto/internal/fips140/subtle/xor_asm.go b/src/crypto/internal/fips140/subtle/xor_asm.go index e10ea8b441429b..bb85aefef4013e 100644 --- a/src/crypto/internal/fips140/subtle/xor_asm.go +++ b/src/crypto/internal/fips140/subtle/xor_asm.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (amd64 || arm64 || mips64 || mips64le || ppc64 || ppc64le || riscv64) && !purego +//go:build (amd64 || arm64 || ppc64 || ppc64le || riscv64) && !purego package subtle diff --git a/src/crypto/internal/fips140/subtle/xor_generic.go b/src/crypto/internal/fips140/subtle/xor_generic.go index 08af84de2a3dab..0b31eec60197d3 100644 --- a/src/crypto/internal/fips140/subtle/xor_generic.go +++ b/src/crypto/internal/fips140/subtle/xor_generic.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (!amd64 && !arm64 && !loong64 && !mips64 && !mips64le && !ppc64 && !ppc64le && !riscv64) || purego +//go:build (!amd64 && !arm64 && !loong64 && !ppc64 && !ppc64le && !riscv64) || purego package subtle diff --git a/src/crypto/internal/fips140/subtle/xor_mips64x.s b/src/crypto/internal/fips140/subtle/xor_mips64x.s deleted file mode 100644 index e580235914aeaf..00000000000000 --- a/src/crypto/internal/fips140/subtle/xor_mips64x.s +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2025 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 (mips64 || mips64le) && !purego - -#include "textflag.h" - -// func xorBytes(dst, a, b *byte, n int) -TEXT ·xorBytes(SB), NOSPLIT|NOFRAME, $0 - MOVV dst+0(FP), R1 - MOVV a+8(FP), R2 - MOVV b+16(FP), R3 - MOVV n+24(FP), R4 - -xor_64_check: - SGTU $64, R4, R5 // R5 = 1 if (64 > R4) - BNE R5, xor_32_check -xor_64: - MOVV (R2), R6 - MOVV 8(R2), R7 - MOVV 16(R2), R8 - MOVV 24(R2), R9 - MOVV (R3), R10 - MOVV 8(R3), R11 - MOVV 16(R3), R12 - MOVV 24(R3), R13 - XOR R6, R10 - XOR R7, R11 - XOR R8, R12 - XOR R9, R13 - MOVV R10, (R1) - MOVV R11, 8(R1) - MOVV R12, 16(R1) - MOVV R13, 24(R1) - MOVV 32(R2), R6 - MOVV 40(R2), R7 - MOVV 48(R2), R8 - MOVV 56(R2), R9 - MOVV 32(R3), R10 - MOVV 40(R3), R11 - MOVV 48(R3), R12 - MOVV 56(R3), R13 - XOR R6, R10 - XOR R7, R11 - XOR R8, R12 - XOR R9, R13 - MOVV R10, 32(R1) - MOVV R11, 40(R1) - MOVV R12, 48(R1) - MOVV R13, 56(R1) - ADDV $64, R2 - ADDV $64, R3 - ADDV $64, R1 - SUBV $64, R4 - SGTU $64, R4, R5 - BEQ R0, R5, xor_64 - BEQ R0, R4, end - -xor_32_check: - SGTU $32, R4, R5 - BNE R5, xor_16_check -xor_32: - MOVV (R2), R6 - MOVV 8(R2), R7 - MOVV 16(R2), R8 - MOVV 24(R2), R9 - MOVV (R3), R10 - MOVV 8(R3), R11 - MOVV 16(R3), R12 - MOVV 24(R3), R13 - XOR R6, R10 - XOR R7, R11 - XOR R8, R12 - XOR R9, R13 - MOVV R10, (R1) - MOVV R11, 8(R1) - MOVV R12, 16(R1) - MOVV R13, 24(R1) - ADDV $32, R2 - ADDV $32, R3 - ADDV $32, R1 - SUBV $32, R4 - BEQ R0, R4, end - -xor_16_check: - SGTU $16, R4, R5 - BNE R5, xor_8_check -xor_16: - MOVV (R2), R6 - MOVV 8(R2), R7 - MOVV (R3), R8 - MOVV 8(R3), R9 - XOR R6, R8 - XOR R7, R9 - MOVV R8, (R1) - MOVV R9, 8(R1) - ADDV $16, R2 - ADDV $16, R3 - ADDV $16, R1 - SUBV $16, R4 - BEQ R0, R4, end - -xor_8_check: - SGTU $8, R4, R5 - BNE R5, xor_4_check -xor_8: - MOVV (R2), R6 - MOVV (R3), R7 - XOR R6, R7 - MOVV R7, (R1) - ADDV $8, R1 - ADDV $8, R2 - ADDV $8, R3 - SUBV $8, R4 - BEQ R0, R4, end - -xor_4_check: - SGTU $4, R4, R5 - BNE R5, xor_2_check -xor_4: - MOVW (R2), R6 - MOVW (R3), R7 - XOR R6, R7 - MOVW R7, (R1) - ADDV $4, R2 - ADDV $4, R3 - ADDV $4, R1 - SUBV $4, R4 - BEQ R0, R4, end - -xor_2_check: - SGTU $2, R4, R5 - BNE R5, xor_1 -xor_2: - MOVH (R2), R6 - MOVH (R3), R7 - XOR R6, R7 - MOVH R7, (R1) - ADDV $2, R2 - ADDV $2, R3 - ADDV $2, R1 - SUBV $2, R4 - BEQ R0, R4, end - -xor_1: - MOVB (R2), R6 - MOVB (R3), R7 - XOR R6, R7 - MOVB R7, (R1) - -end: - RET From 8097b1915f617167f3b12b03e78a23859d256eb6 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 21 Oct 2025 16:14:03 +0200 Subject: [PATCH 49/59] [release-branch.go1.25] os: support deleting read-only files in RemoveAll on older Windows versions The Windows implementation of RemoveAll supports deleting read-only files only on file systems that supports POSIX semantics and on newer Windows versions (Windows 10 RS5 and latter). For all the other cases, the read-only bit was not clearer before deleting read-only files, so they fail to delete. Note that this case was supported prior to CL 75922, which landed on Go 1.25. For #75922 Fixes #75989 Change-Id: Id6e6477f42e1952d08318ca3e4ab7c1648969f66 Reviewed-on: https://go-review.googlesource.com/c/go/+/713480 LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase Reviewed-by: Damien Neil Auto-Submit: Damien Neil (cherry picked from commit b31dc77ceab962c0f4f5e4a9fc5e1a403fbd2d7c) Reviewed-on: https://go-review.googlesource.com/c/go/+/715360 Auto-Submit: Michael Knyszek --- src/internal/syscall/windows/at_windows.go | 77 ++++++++++++++----- .../syscall/windows/symlink_windows.go | 1 + .../syscall/windows/syscall_windows.go | 5 ++ src/internal/syscall/windows/types_windows.go | 5 ++ .../syscall/windows/zsyscall_windows.go | 10 +++ src/os/path_windows_test.go | 17 ++++ 6 files changed, 95 insertions(+), 20 deletions(-) diff --git a/src/internal/syscall/windows/at_windows.go b/src/internal/syscall/windows/at_windows.go index d48fce1c99dc36..41cdaf0d2e34ca 100644 --- a/src/internal/syscall/windows/at_windows.go +++ b/src/internal/syscall/windows/at_windows.go @@ -204,7 +204,7 @@ func Deleteat(dirfd syscall.Handle, name string, options uint32) error { var h syscall.Handle err := NtOpenFile( &h, - SYNCHRONIZE|DELETE, + SYNCHRONIZE|FILE_READ_ATTRIBUTES|DELETE, objAttrs, &IO_STATUS_BLOCK{}, FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE, @@ -215,14 +215,22 @@ func Deleteat(dirfd syscall.Handle, name string, options uint32) error { } defer syscall.CloseHandle(h) - const ( - FileDispositionInformation = 13 - FileDispositionInformationEx = 64 - ) + if TestDeleteatFallback { + return deleteatFallback(h) + } + + const FileDispositionInformationEx = 64 // First, attempt to delete the file using POSIX semantics // (which permit a file to be deleted while it is still open). // This matches the behavior of DeleteFileW. + // + // The following call uses features available on different Windows versions: + // - FILE_DISPOSITION_INFORMATION_EX: Windows 10, version 1607 (aka RS1) + // - FILE_DISPOSITION_POSIX_SEMANTICS: Windows 10, version 1607 (aka RS1) + // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: Windows 10, version 1809 (aka RS5) + // + // Also, some file systems, like FAT32, don't support POSIX semantics. err = NtSetInformationFile( h, &IO_STATUS_BLOCK{}, @@ -241,28 +249,57 @@ func Deleteat(dirfd syscall.Handle, name string, options uint32) error { switch err { case nil: return nil - case STATUS_CANNOT_DELETE, STATUS_DIRECTORY_NOT_EMPTY: + case STATUS_INVALID_INFO_CLASS, // the operating system doesn't support FileDispositionInformationEx + STATUS_INVALID_PARAMETER, // the operating system doesn't support one of the flags + STATUS_NOT_SUPPORTED: // the file system doesn't support FILE_DISPOSITION_INFORMATION_EX or one of the flags + return deleteatFallback(h) + default: return err.(NTStatus).Errno() } +} - // If the prior deletion failed, the filesystem either doesn't support - // POSIX semantics (for example, FAT), or hasn't implemented - // FILE_DISPOSITION_INFORMATION_EX. - // - // Try again. - err = NtSetInformationFile( +// TestDeleteatFallback should only be used for testing purposes. +// When set, [Deleteat] uses the fallback path unconditionally. +var TestDeleteatFallback bool + +// deleteatFallback is a deleteat implementation that strives +// for compatibility with older Windows versions and file systems +// over performance. +func deleteatFallback(h syscall.Handle) error { + var data syscall.ByHandleFileInformation + if err := syscall.GetFileInformationByHandle(h, &data); err == nil && data.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { + // Remove read-only attribute. Reopen the file, as it was previously open without FILE_WRITE_ATTRIBUTES access + // in order to maximize compatibility in the happy path. + wh, err := ReOpenFile(h, + FILE_WRITE_ATTRIBUTES, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, + ) + if err != nil { + return err + } + err = SetFileInformationByHandle( + wh, + FileBasicInfo, + unsafe.Pointer(&FILE_BASIC_INFO{ + FileAttributes: data.FileAttributes &^ FILE_ATTRIBUTE_READONLY, + }), + uint32(unsafe.Sizeof(FILE_BASIC_INFO{})), + ) + syscall.CloseHandle(wh) + if err != nil { + return err + } + } + + return SetFileInformationByHandle( h, - &IO_STATUS_BLOCK{}, - unsafe.Pointer(&FILE_DISPOSITION_INFORMATION{ + FileDispositionInfo, + unsafe.Pointer(&FILE_DISPOSITION_INFO{ DeleteFile: true, }), - uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION{})), - FileDispositionInformation, + uint32(unsafe.Sizeof(FILE_DISPOSITION_INFO{})), ) - if st, ok := err.(NTStatus); ok { - return st.Errno() - } - return err } func Renameat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, newpath string) error { diff --git a/src/internal/syscall/windows/symlink_windows.go b/src/internal/syscall/windows/symlink_windows.go index b91246037b5efe..b8249b3848ea02 100644 --- a/src/internal/syscall/windows/symlink_windows.go +++ b/src/internal/syscall/windows/symlink_windows.go @@ -19,6 +19,7 @@ const ( FileBasicInfo = 0 // FILE_BASIC_INFO FileStandardInfo = 1 // FILE_STANDARD_INFO FileNameInfo = 2 // FILE_NAME_INFO + FileDispositionInfo = 4 // FILE_DISPOSITION_INFO FileStreamInfo = 7 // FILE_STREAM_INFO FileCompressionInfo = 8 // FILE_COMPRESSION_INFO FileAttributeTagInfo = 9 // FILE_ATTRIBUTE_TAG_INFO diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go index 905cabc81e4798..c34cc795a0ea90 100644 --- a/src/internal/syscall/windows/syscall_windows.go +++ b/src/internal/syscall/windows/syscall_windows.go @@ -529,6 +529,8 @@ const ( //sys GetOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, done *uint32, wait bool) (err error) //sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW +//sys ReOpenFile(filehandle syscall.Handle, desiredAccess uint32, shareMode uint32, flagAndAttributes uint32) (handle syscall.Handle, err error) + // NTStatus corresponds with NTSTATUS, error values returned by ntdll.dll and // other native functions. type NTStatus uint32 @@ -554,6 +556,9 @@ const ( STATUS_NOT_A_DIRECTORY NTStatus = 0xC0000103 STATUS_CANNOT_DELETE NTStatus = 0xC0000121 STATUS_REPARSE_POINT_ENCOUNTERED NTStatus = 0xC000050B + STATUS_NOT_SUPPORTED NTStatus = 0xC00000BB + STATUS_INVALID_PARAMETER NTStatus = 0xC000000D + STATUS_INVALID_INFO_CLASS NTStatus = 0xC0000003 ) const ( diff --git a/src/internal/syscall/windows/types_windows.go b/src/internal/syscall/windows/types_windows.go index 93664b4b7da8ca..6d989e7e7e78bc 100644 --- a/src/internal/syscall/windows/types_windows.go +++ b/src/internal/syscall/windows/types_windows.go @@ -199,6 +199,11 @@ const ( FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000 ) +// https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_disposition_info +type FILE_DISPOSITION_INFO struct { + DeleteFile bool +} + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_disposition_information type FILE_DISPOSITION_INFORMATION struct { DeleteFile bool diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go index 90cf0b92a49bc4..b3f01ef5c00281 100644 --- a/src/internal/syscall/windows/zsyscall_windows.go +++ b/src/internal/syscall/windows/zsyscall_windows.go @@ -85,6 +85,7 @@ var ( procModule32NextW = modkernel32.NewProc("Module32NextW") procMoveFileExW = modkernel32.NewProc("MoveFileExW") procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar") + procReOpenFile = modkernel32.NewProc("ReOpenFile") procRtlLookupFunctionEntry = modkernel32.NewProc("RtlLookupFunctionEntry") procRtlVirtualUnwind = modkernel32.NewProc("RtlVirtualUnwind") procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle") @@ -431,6 +432,15 @@ func MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, return } +func ReOpenFile(filehandle syscall.Handle, desiredAccess uint32, shareMode uint32, flagAndAttributes uint32) (handle syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall6(procReOpenFile.Addr(), 4, uintptr(filehandle), uintptr(desiredAccess), uintptr(shareMode), uintptr(flagAndAttributes), 0, 0) + handle = syscall.Handle(r0) + if handle == 0 { + err = errnoErr(e1) + } + return +} + func RtlLookupFunctionEntry(pc uintptr, baseAddress *uintptr, table unsafe.Pointer) (ret *RUNTIME_FUNCTION) { r0, _, _ := syscall.Syscall(procRtlLookupFunctionEntry.Addr(), 3, uintptr(pc), uintptr(unsafe.Pointer(baseAddress)), uintptr(table)) ret = (*RUNTIME_FUNCTION)(unsafe.Pointer(r0)) diff --git a/src/os/path_windows_test.go b/src/os/path_windows_test.go index 3fa02e2a65b083..eea2b58ee0a886 100644 --- a/src/os/path_windows_test.go +++ b/src/os/path_windows_test.go @@ -236,6 +236,23 @@ func TestRemoveAllLongPathRelative(t *testing.T) { } } +func TestRemoveAllFallback(t *testing.T) { + windows.TestDeleteatFallback = true + t.Cleanup(func() { windows.TestDeleteatFallback = false }) + + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "file1"), []byte{}, 0700); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "file2"), []byte{}, 0400); err != nil { // read-only file + t.Fatal(err) + } + + if err := os.RemoveAll(dir); err != nil { + t.Fatal(err) + } +} + func testLongPathAbs(t *testing.T, target string) { t.Helper() testWalkFn := func(path string, info os.FileInfo, err error) error { From 5ba37a3677d322c442ab77f94d4f92f1acc67dba Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Wed, 22 Oct 2025 10:13:44 -0700 Subject: [PATCH 50/59] [release-branch.go1.25] cmd/compile: don't optimize away a panicing interface comparison We can't do direct pointer comparisons if the type is not a comparable type. Fixes #76010 Change-Id: I1687acff21832d2c2e8f3b875e7b5ec125702ef3 Reviewed-on: https://go-review.googlesource.com/c/go/+/713840 LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase Reviewed-by: Cuong Manh Le Reviewed-by: Keith Randall Reviewed-on: https://go-review.googlesource.com/c/go/+/715720 Reviewed-by: Michael Knyszek --- src/cmd/compile/internal/ssa/rewrite.go | 10 ++++--- test/fixedbugs/issue76008.go | 35 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 test/fixedbugs/issue76008.go diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index eb2c3b31b8c998..4834f833c2f553 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -2555,7 +2555,7 @@ func rewriteStructStore(v *Value) *Value { // isDirectType reports whether v represents a type // (a *runtime._type) whose value is stored directly in an -// interface (i.e., is pointer or pointer-like). +// interface (i.e., is pointer or pointer-like) and is comparable. func isDirectType(v *Value) bool { return isDirectType1(v) } @@ -2571,7 +2571,8 @@ func isDirectType1(v *Value) bool { return false } if ti, ok := (*lsym.Extra).(*obj.TypeInfo); ok { - return types.IsDirectIface(ti.Type.(*types.Type)) + t := ti.Type.(*types.Type) + return types.IsDirectIface(t) && types.IsComparable(t) } } return false @@ -2588,7 +2589,7 @@ func isDirectType2(v *Value) bool { // isDirectIface reports whether v represents an itab // (a *runtime._itab) for a type whose value is stored directly -// in an interface (i.e., is pointer or pointer-like). +// in an interface (i.e., is pointer or pointer-like) and is comparable. func isDirectIface(v *Value) bool { return isDirectIface1(v, 9) } @@ -2607,7 +2608,8 @@ func isDirectIface1(v *Value, depth int) bool { return false } if ii, ok := (*lsym.Extra).(*obj.ItabInfo); ok { - return types.IsDirectIface(ii.Type.(*types.Type)) + t := ii.Type.(*types.Type) + return types.IsDirectIface(t) && types.IsComparable(t) } case OpConstNil: // We can treat this as direct, because if the itab is diff --git a/test/fixedbugs/issue76008.go b/test/fixedbugs/issue76008.go new file mode 100644 index 00000000000000..bdf273bca1e81f --- /dev/null +++ b/test/fixedbugs/issue76008.go @@ -0,0 +1,35 @@ +// run + +// Copyright 2025 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 main + +import "runtime" + +func main() { + shouldPanic(func() { + g = any(func() {}) == any(func() {}) + }) + shouldPanic(func() { + g = any(map[int]int{}) == any(map[int]int{}) + }) + shouldPanic(func() { + g = any([]int{}) == any([]int{}) + }) +} + +var g bool + +func shouldPanic(f func()) { + defer func() { + err := recover() + if err == nil { + _, _, line, _ := runtime.Caller(2) + println("did not panic at line", line+1) + } + }() + + f() +} From 83885f3c22242f3e4499bb5b12892bc7ba11a74b Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Thu, 23 Oct 2025 08:16:39 -0700 Subject: [PATCH 51/59] [release-branch.go1.25] encoding/pem: properly calculate end indexes When a block is missing the END line trailer, calculate the indexes of the end and end trailer _before_ continuing the loop, making the reslicing at the start of the loop work as expected. Fixes #76029 Change-Id: If45c8cb473315623618f02cc7609f517a72d232d Reviewed-on: https://go-review.googlesource.com/c/go/+/714200 Auto-Submit: Roland Shoemaker Reviewed-by: Damien Neil LUCI-TryBot-Result: Go LUCI (cherry picked from commit 839da71f8907ac4434299db4353db31835c916df) Reviewed-on: https://go-review.googlesource.com/c/go/+/714661 Reviewed-by: David Chase --- src/encoding/pem/pem.go | 7 +++++-- src/encoding/pem/pem_test.go | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/encoding/pem/pem.go b/src/encoding/pem/pem.go index 2356096ade24d2..6bf2b41ad0eb7f 100644 --- a/src/encoding/pem/pem.go +++ b/src/encoding/pem/pem.go @@ -95,6 +95,9 @@ func Decode(data []byte) (p *Block, rest []byte) { for { // If we've already tried parsing a block, skip past the END we already // saw. + if endTrailerIndex < 0 || endTrailerIndex > len(rest) { + return nil, data + } rest = rest[endTrailerIndex:] // Find the first END line, and then find the last BEGIN line before @@ -116,11 +119,11 @@ func Decode(data []byte) (p *Block, rest []byte) { var typeLine []byte var consumed int typeLine, rest, consumed = getLine(rest) + endIndex -= consumed + endTrailerIndex -= consumed if !bytes.HasSuffix(typeLine, pemEndOfLine) { continue } - endIndex -= consumed - endTrailerIndex -= consumed typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)] p = &Block{ diff --git a/src/encoding/pem/pem_test.go b/src/encoding/pem/pem_test.go index 5bdc2f66a7b3ab..fa6e8ba62bdb87 100644 --- a/src/encoding/pem/pem_test.go +++ b/src/encoding/pem/pem_test.go @@ -736,3 +736,7 @@ func FuzzDecode(f *testing.F) { Decode(data) }) } + +func TestMissingEndTrailer(t *testing.T) { + Decode([]byte{0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xa, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20}) +} From f2cd93aa0505465c1d30201c806b6d4d3481c5fa Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Wed, 5 Nov 2025 10:58:53 -0800 Subject: [PATCH 52/59] [release-branch.go1.25] go1.25.4 Change-Id: Iddc4427830693f0b518cb9766d6b1b552b97b79e Reviewed-on: https://go-review.googlesource.com/c/go/+/718064 Reviewed-by: Michael Knyszek Reviewed-by: Michael Pratt TryBot-Bypass: Gopher Robot Auto-Submit: Gopher Robot --- VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 05d3fa7c5e8101..e54793d74a8ff1 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -go1.25.3 -time 2025-10-13T16:08:43Z +go1.25.4 +time 2025-10-31T13:24:27Z From 433c01e94efe72f985096839d0631103b3163be6 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Mon, 3 Nov 2025 16:29:06 +0100 Subject: [PATCH 53/59] [release-branch.go1.25] internal/syscall/windows: fix ReOpenFile sentinel error value ReOpenFile is documented to return INVALID_HANDLE_VALUE on error, but the previous definition was checking for 0 instead. ReOpenFile was added to the go1.25 release branch in CL 715360. This new CL amends it. Fixes #76360 Updates #75989 Change-Id: Idec5e75e40b9f6c409e068d63a9b606781e80a46 Reviewed-on: https://go-review.googlesource.com/c/go/+/717320 Auto-Submit: Quim Muntal LUCI-TryBot-Result: Go LUCI Reviewed-by: Damien Neil Reviewed-by: Alex Brainman Reviewed-by: Michael Pratt (cherry picked from commit CL 717320) Reviewed-on: https://go-review.googlesource.com/c/go/+/718000 Reviewed-by: Dmitri Shuralyov --- src/internal/syscall/windows/syscall_windows.go | 2 +- src/internal/syscall/windows/zsyscall_windows.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go index c34cc795a0ea90..968dbaf061e4e7 100644 --- a/src/internal/syscall/windows/syscall_windows.go +++ b/src/internal/syscall/windows/syscall_windows.go @@ -529,7 +529,7 @@ const ( //sys GetOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, done *uint32, wait bool) (err error) //sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW -//sys ReOpenFile(filehandle syscall.Handle, desiredAccess uint32, shareMode uint32, flagAndAttributes uint32) (handle syscall.Handle, err error) +//sys ReOpenFile(filehandle syscall.Handle, desiredAccess uint32, shareMode uint32, flagAndAttributes uint32) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] // NTStatus corresponds with NTSTATUS, error values returned by ntdll.dll and // other native functions. diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go index b3f01ef5c00281..ba6c017ea96458 100644 --- a/src/internal/syscall/windows/zsyscall_windows.go +++ b/src/internal/syscall/windows/zsyscall_windows.go @@ -435,7 +435,7 @@ func MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, func ReOpenFile(filehandle syscall.Handle, desiredAccess uint32, shareMode uint32, flagAndAttributes uint32) (handle syscall.Handle, err error) { r0, _, e1 := syscall.Syscall6(procReOpenFile.Addr(), 4, uintptr(filehandle), uintptr(desiredAccess), uintptr(shareMode), uintptr(flagAndAttributes), 0, 0) handle = syscall.Handle(r0) - if handle == 0 { + if handle == syscall.InvalidHandle { err = errnoErr(e1) } return From e1ce1bfa7f0d44e864d8ea6d6cec62c09668ad66 Mon Sep 17 00:00:00 2001 From: Julien Cretel Date: Mon, 10 Nov 2025 21:20:09 +0000 Subject: [PATCH 54/59] [release-branch.go1.25] mime: parse media types that contain braces This CL fixes a bug introduced by CL 666655: isTokenChar would no longer (but should) report true for '{' and '}'. Fixes #76245 Change-Id: Ifc0953c30d7cae7bfba9bc4b6bb6951a83c52576 GitHub-Last-Rev: c91a75c2c8778a9a8343c6bb4fa89eb1f978059f GitHub-Pull-Request: golang/go#76243 Reviewed-on: https://go-review.googlesource.com/c/go/+/719380 Reviewed-by: Sean Liao Reviewed-by: Jorropo Reviewed-by: Michael Knyszek Reviewed-by: Damien Neil LUCI-TryBot-Result: Go LUCI (cherry picked from commit c761b26b56eec36390885e5373aab2fd17dc67ef) Reviewed-on: https://go-review.googlesource.com/c/go/+/721000 Reviewed-by: Junyang Shao --- src/mime/grammar.go | 2 ++ src/mime/mediatype_test.go | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/mime/grammar.go b/src/mime/grammar.go index cc578fbcfd4168..1efd8a16dec607 100644 --- a/src/mime/grammar.go +++ b/src/mime/grammar.go @@ -62,7 +62,9 @@ func isTokenChar(c byte) bool { 1<<'^' | 1<<'_' | 1<<'`' | + 1<<'{' | 1<<'|' | + 1<<'}' | 1<<'~' return ((uint64(1)<>64)) != 0 diff --git a/src/mime/mediatype_test.go b/src/mime/mediatype_test.go index 251df8d6691ab9..da8d64de7a3f0c 100644 --- a/src/mime/mediatype_test.go +++ b/src/mime/mediatype_test.go @@ -413,6 +413,9 @@ func init() { // Issue #48866: duplicate parameters containing equal values should be allowed {`text; charset=utf-8; charset=utf-8; format=fixed`, "text", m("charset", "utf-8", "format", "fixed")}, {`text; charset=utf-8; format=flowed; charset=utf-8`, "text", m("charset", "utf-8", "format", "flowed")}, + + // Issue #76236: '{' and '}' are token chars. + {"attachment; filename={file}.png", "attachment", m("filename", "{file}.png")}, } } From 287017acebd27203aa3218abbd11ed65c2280cf8 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Mon, 24 Nov 2025 08:46:08 -0800 Subject: [PATCH 55/59] [release-branch.go1.25] crypto/x509: excluded subdomain constraints preclude wildcard SANs When evaluating name constraints in a certificate chain, the presence of an excluded subdomain constraint (e.g., excluding "test.example.com") should preclude the use of a wildcard SAN (e.g., "*.example.com"). Fixes #76442 Fixes #76464 Fixes CVE-2025-61727 Change-Id: I42a0da010cb36d2ec9d1239ae3f61cf25eb78bba Reviewed-on: https://go-review.googlesource.com/c/go/+/724400 Reviewed-by: Nicholas Husin Reviewed-by: Nicholas Husin Reviewed-by: Daniel McCarney LUCI-TryBot-Result: Go LUCI Reviewed-by: Neal Patel --- src/crypto/x509/name_constraints_test.go | 34 ++++++++++++++++++++ src/crypto/x509/verify.go | 40 +++++++++++++++--------- src/crypto/x509/verify_test.go | 2 +- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go index a5851845164d10..bc91b28401fce5 100644 --- a/src/crypto/x509/name_constraints_test.go +++ b/src/crypto/x509/name_constraints_test.go @@ -1624,6 +1624,40 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "URI with IP", }, + // #87: subdomain excluded constraints preclude wildcard names + { + roots: []constraintsSpec{ + { + bad: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.example.com"}, + }, + expectedError: "\"*.example.com\" is excluded by constraint \"foo.example.com\"", + }, + // #88: wildcard names are not matched by subdomain permitted constraints + { + roots: []constraintsSpec{ + { + ok: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.example.com"}, + }, + expectedError: "\"*.example.com\" is not permitted", + }, } func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) { diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index bf7e7ec058db2b..9175fa4dc147a2 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -429,7 +429,7 @@ func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { return reverseLabels, true } -func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { +func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { // If the constraint contains an @, then it specifies an exact mailbox // name. if strings.Contains(constraint, "@") { @@ -442,10 +442,10 @@ func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDom // Otherwise the constraint is like a DNS constraint of the domain part // of the mailbox. - return matchDomainConstraint(mailbox.domain, constraint, reversedDomainsCache, reversedConstraintsCache) + return matchDomainConstraint(mailbox.domain, constraint, excluded, reversedDomainsCache, reversedConstraintsCache) } -func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { +func matchURIConstraint(uri *url.URL, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { // From RFC 5280, Section 4.2.1.10: // “a uniformResourceIdentifier that does not include an authority // component with a host name specified as a fully qualified domain @@ -474,7 +474,7 @@ func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache ma return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String()) } - return matchDomainConstraint(host, constraint, reversedDomainsCache, reversedConstraintsCache) + return matchDomainConstraint(host, constraint, excluded, reversedDomainsCache, reversedConstraintsCache) } func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { @@ -491,7 +491,7 @@ func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { return true, nil } -func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { +func matchDomainConstraint(domain, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { // The meaning of zero length constraints is not specified, but this // code follows NSS and accepts them as matching everything. if len(constraint) == 0 { @@ -508,6 +508,11 @@ func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[s reversedDomainsCache[domain] = domainLabels } + wildcardDomain := false + if len(domain) > 0 && domain[0] == '*' { + wildcardDomain = true + } + // RFC 5280 says that a leading period in a domain name means that at // least one label must be prepended, but only for URI and email // constraints, not DNS constraints. The code also supports that @@ -534,6 +539,11 @@ func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[s return false, nil } + if excluded && wildcardDomain && len(domainLabels) > 1 && len(constraintLabels) > 0 { + domainLabels = domainLabels[:len(domainLabels)-1] + constraintLabels = constraintLabels[:len(constraintLabels)-1] + } + for i, constraintLabel := range constraintLabels { if !strings.EqualFold(constraintLabel, domainLabels[i]) { return false, nil @@ -553,7 +563,7 @@ func (c *Certificate) checkNameConstraints(count *int, nameType string, name string, parsedName any, - match func(parsedName, constraint any) (match bool, err error), + match func(parsedName, constraint any, excluded bool) (match bool, err error), permitted, excluded any) error { excludedValue := reflect.ValueOf(excluded) @@ -565,7 +575,7 @@ func (c *Certificate) checkNameConstraints(count *int, for i := 0; i < excludedValue.Len(); i++ { constraint := excludedValue.Index(i).Interface() - match, err := match(parsedName, constraint) + match, err := match(parsedName, constraint, true) if err != nil { return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} } @@ -587,7 +597,7 @@ func (c *Certificate) checkNameConstraints(count *int, constraint := permittedValue.Index(i).Interface() var err error - if ok, err = match(parsedName, constraint); err != nil { + if ok, err = match(parsedName, constraint, false); err != nil { return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} } @@ -679,8 +689,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox, - func(parsedName, constraint any) (bool, error) { - return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), reversedDomainsCache, reversedConstraintsCache) + func(parsedName, constraint any, excluded bool) (bool, error) { + return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil { return err } @@ -692,8 +702,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name, - func(parsedName, constraint any) (bool, error) { - return matchDomainConstraint(parsedName.(string), constraint.(string), reversedDomainsCache, reversedConstraintsCache) + func(parsedName, constraint any, excluded bool) (bool, error) { + return matchDomainConstraint(parsedName.(string), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil { return err } @@ -706,8 +716,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri, - func(parsedName, constraint any) (bool, error) { - return matchURIConstraint(parsedName.(*url.URL), constraint.(string), reversedDomainsCache, reversedConstraintsCache) + func(parsedName, constraint any, excluded bool) (bool, error) { + return matchURIConstraint(parsedName.(*url.URL), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil { return err } @@ -719,7 +729,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip, - func(parsedName, constraint any) (bool, error) { + func(parsedName, constraint any, _ bool) (bool, error) { return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet)) }, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil { return err diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go index 60a4cea9146adf..6a394e46e94f5a 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -1352,7 +1352,7 @@ var nameConstraintTests = []struct { func TestNameConstraints(t *testing.T) { for i, test := range nameConstraintTests { - result, err := matchDomainConstraint(test.domain, test.constraint, map[string][]string{}, map[string][]string{}) + result, err := matchDomainConstraint(test.domain, test.constraint, false, map[string][]string{}, map[string][]string{}) if err != nil && !test.expectError { t.Errorf("unexpected error for test #%d: domain=%s, constraint=%s, err=%s", i, test.domain, test.constraint, err) From f7bce4bd6f7b13de8d9f06f7f262e3b60381e7e9 Mon Sep 17 00:00:00 2001 From: "Nicholas S. Husin" Date: Mon, 24 Nov 2025 14:56:23 -0500 Subject: [PATCH 56/59] [release-branch.go1.25] crypto/x509: prevent HostnameError.Error() from consuming excessive resource Constructing HostnameError.Error() takes O(N^2) runtime due to using a string concatenation in a loop. Additionally, there is no limit on how many names are included in the error message. As a result, a malicious attacker could craft a certificate with an infinite amount of names to unfairly consume resource. To remediate this, we will now use strings.Builder to construct the error message, preventing O(N^2) runtime. When a certificate has 100 or more names, we will also not print each name individually. Thanks to Philippe Antoine (Catena cyber) for reporting this issue. Updates #76445 Fixes #76461 Fixes CVE-2025-61729 Change-Id: I6343776ec3289577abc76dad71766c491c1a7c81 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3000 Reviewed-by: Neal Patel Reviewed-by: Damien Neil Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3200 Reviewed-by: Roland Shoemaker Reviewed-on: https://go-review.googlesource.com/c/go/+/725800 TryBot-Bypass: Dmitri Shuralyov Reviewed-by: Mark Freeman Reviewed-by: Dmitri Shuralyov Auto-Submit: Dmitri Shuralyov --- src/crypto/x509/verify.go | 21 ++++++++++----- src/crypto/x509/verify_test.go | 47 ++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index 9175fa4dc147a2..3de9f93b2c4b16 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -110,31 +110,38 @@ type HostnameError struct { func (h HostnameError) Error() string { c := h.Certificate + maxNamesIncluded := 100 if !c.hasSANExtension() && matchHostnames(c.Subject.CommonName, h.Host) { return "x509: certificate relies on legacy Common Name field, use SANs instead" } - var valid string + var valid strings.Builder if ip := net.ParseIP(h.Host); ip != nil { // Trying to validate an IP if len(c.IPAddresses) == 0 { return "x509: cannot validate certificate for " + h.Host + " because it doesn't contain any IP SANs" } + if len(c.IPAddresses) >= maxNamesIncluded { + return fmt.Sprintf("x509: certificate is valid for %d IP SANs, but none matched %s", len(c.IPAddresses), h.Host) + } for _, san := range c.IPAddresses { - if len(valid) > 0 { - valid += ", " + if valid.Len() > 0 { + valid.WriteString(", ") } - valid += san.String() + valid.WriteString(san.String()) } } else { - valid = strings.Join(c.DNSNames, ", ") + if len(c.DNSNames) >= maxNamesIncluded { + return fmt.Sprintf("x509: certificate is valid for %d names, but none matched %s", len(c.DNSNames), h.Host) + } + valid.WriteString(strings.Join(c.DNSNames, ", ")) } - if len(valid) == 0 { + if valid.Len() == 0 { return "x509: certificate is not valid for any names, but wanted to match " + h.Host } - return "x509: certificate is valid for " + valid + ", not " + h.Host + return "x509: certificate is valid for " + valid.String() + ", not " + h.Host } // UnknownAuthorityError results when the certificate issuer is unknown diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go index 6a394e46e94f5a..9a21218ee4b465 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -10,13 +10,16 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/rsa" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "errors" "fmt" "internal/testenv" + "log" "math/big" + "net" "os" "os/exec" "runtime" @@ -89,6 +92,26 @@ var verifyTests = []verifyTest{ errorCallback: expectHostnameError("certificate is valid for"), }, + { + name: "TooManyDNS", + leaf: generatePEMCertWithRepeatSAN(1677615892, 200, "fake.dns"), + roots: []string{generatePEMCertWithRepeatSAN(1677615892, 200, "fake.dns")}, + currentTime: 1677615892, + dnsName: "www.example.com", + systemSkip: true, // does not chain to a system root + + errorCallback: expectHostnameError("certificate is valid for 200 names, but none matched"), + }, + { + name: "TooManyIPs", + leaf: generatePEMCertWithRepeatSAN(1677615892, 150, "4.3.2.1"), + roots: []string{generatePEMCertWithRepeatSAN(1677615892, 150, "4.3.2.1")}, + currentTime: 1677615892, + dnsName: "1.2.3.4", + systemSkip: true, // does not chain to a system root + + errorCallback: expectHostnameError("certificate is valid for 150 IP SANs, but none matched"), + }, { name: "IPMissing", leaf: googleLeaf, @@ -552,6 +575,30 @@ func nameToKey(name *pkix.Name) string { return strings.Join(name.Country, ",") + "/" + strings.Join(name.Organization, ",") + "/" + strings.Join(name.OrganizationalUnit, ",") + "/" + name.CommonName } +func generatePEMCertWithRepeatSAN(currentTime int64, count int, san string) string { + cert := Certificate{ + NotBefore: time.Unix(currentTime, 0), + NotAfter: time.Unix(currentTime, 0), + } + if ip := net.ParseIP(san); ip != nil { + cert.IPAddresses = slices.Repeat([]net.IP{ip}, count) + } else { + cert.DNSNames = slices.Repeat([]string{san}, count) + } + privKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + log.Fatal(err) + } + certBytes, err := CreateCertificate(rand.Reader, &cert, &cert, &privKey.PublicKey, privKey) + if err != nil { + log.Fatal(err) + } + return string(pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + })) +} + const gtsIntermediate = `-----BEGIN CERTIFICATE----- MIIFljCCA36gAwIBAgINAgO8U1lrNMcY9QFQZjANBgkqhkiG9w0BAQsFADBHMQsw CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU From fefb02adf45c4bcc879bd406a8d61f2a292c26a9 Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Tue, 2 Dec 2025 08:00:45 -0800 Subject: [PATCH 57/59] [release-branch.go1.25] go1.25.5 Change-Id: If484d63fd8cc5ea0872780019535368afcf4ec5b Reviewed-on: https://go-review.googlesource.com/c/go/+/725842 Auto-Submit: Gopher Robot Reviewed-by: Dmitri Shuralyov Reviewed-by: Mark Freeman TryBot-Bypass: Gopher Robot --- VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index e54793d74a8ff1..9e2157943c8db8 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -go1.25.4 -time 2025-10-31T13:24:27Z +go1.25.5 +time 2025-11-26T02:01:51Z From d1438a5cd379a348af025f85f8c429b6888b288f Mon Sep 17 00:00:00 2001 From: Tim Cooijmans Date: Tue, 30 Sep 2025 21:53:11 +0000 Subject: [PATCH 58/59] cmd/cgo: fix unaligned arguments typedmemmove crash on iOS Irregularly typedmemmove and bulkBarrierPreWrite crashes on unaligned arguments. By aligning the arguments this is fixed. Fixes #46893 Change-Id: I7beb9fdc31053fcb71bee6c6cb906dea31718c56 GitHub-Last-Rev: 46ae8b96889644aab60ea4284cf447a740354c6a GitHub-Pull-Request: golang/go#74868 Reviewed-on: https://go-review.googlesource.com/c/go/+/692935 Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall Reviewed-by: Keith Randall --- src/cmd/cgo/internal/testout/out_test.go | 144 ++++++++++++++++++ .../cgo/internal/testout/testdata/aligned.go | 63 ++++++++ src/cmd/cgo/out.go | 13 +- 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 src/cmd/cgo/internal/testout/out_test.go create mode 100644 src/cmd/cgo/internal/testout/testdata/aligned.go diff --git a/src/cmd/cgo/internal/testout/out_test.go b/src/cmd/cgo/internal/testout/out_test.go new file mode 100644 index 00000000000000..81dfa365871372 --- /dev/null +++ b/src/cmd/cgo/internal/testout/out_test.go @@ -0,0 +1,144 @@ +// Copyright 2025 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 out_test + +import ( + "bufio" + "bytes" + "fmt" + "internal/testenv" + "internal/goarch" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "testing" +) + +type methodAlign struct { + Method string + Align int +} + +var wantAligns = map[string]int{ + "ReturnEmpty": 1, + "ReturnOnlyUint8": 1, + "ReturnOnlyUint16": 2, + "ReturnOnlyUint32": 4, + "ReturnOnlyUint64": goarch.PtrSize, + "ReturnOnlyInt": goarch.PtrSize, + "ReturnOnlyPtr": goarch.PtrSize, + "ReturnByteSlice": goarch.PtrSize, + "ReturnString": goarch.PtrSize, + "InputAndReturnUint8": 1, + "MixedTypes": goarch.PtrSize, +} + +// TestAligned tests that the generated _cgo_export.c file has the wanted +// align attributes for struct types used as arguments or results of +// //exported functions. +func TestAligned(t *testing.T) { + testenv.MustHaveGoRun(t) + testenv.MustHaveCGO(t) + + testdata, err := filepath.Abs("testdata") + if err != nil { + t.Fatal(err) + } + + objDir := t.TempDir() + + cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "cgo", + "-objdir", objDir, + filepath.Join(testdata, "aligned.go")) + cmd.Stderr = new(bytes.Buffer) + + err = cmd.Run() + if err != nil { + t.Fatalf("%#q: %v\n%s", cmd, err, cmd.Stderr) + } + + haveAligns, err := parseAlign(filepath.Join(objDir, "_cgo_export.c")) + if err != nil { + t.Fatal(err) + } + + // Check that we have all the wanted methods + if len(haveAligns) != len(wantAligns) { + t.Fatalf("have %d methods with aligned, want %d", len(haveAligns), len(wantAligns)) + } + + for i := range haveAligns { + method := haveAligns[i].Method + haveAlign := haveAligns[i].Align + + wantAlign, ok := wantAligns[method] + if !ok { + t.Errorf("method %s: have aligned %d, want missing entry", method, haveAlign) + } else if haveAlign != wantAlign { + t.Errorf("method %s: have aligned %d, want %d", method, haveAlign, wantAlign) + } + } +} + +func parseAlign(filename string) ([]methodAlign, error) { + file, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + var results []methodAlign + scanner := bufio.NewScanner(file) + + // Regex to match function declarations like "struct MethodName_return MethodName(" + funcRegex := regexp.MustCompile(`^struct\s+(\w+)_return\s+(\w+)\(`) + // Regex to match simple function declarations like "GoSlice MethodName(" + simpleFuncRegex := regexp.MustCompile(`^Go\w+\s+(\w+)\(`) + // Regex to match void-returning exported functions like "void ReturnEmpty(" + voidFuncRegex := regexp.MustCompile(`^void\s+(\w+)\(`) + // Regex to match align attributes like "__attribute__((aligned(8)))" + alignRegex := regexp.MustCompile(`__attribute__\(\(aligned\((\d+)\)\)\)`) + + var currentMethod string + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + // Check if this line declares a function with struct return type + if matches := funcRegex.FindStringSubmatch(line); matches != nil { + currentMethod = matches[2] // Extract the method name + } else if matches := simpleFuncRegex.FindStringSubmatch(line); matches != nil { + // Check if this line declares a function with simple return type (like GoSlice) + currentMethod = matches[1] // Extract the method name + } else if matches := voidFuncRegex.FindStringSubmatch(line); matches != nil { + // Check if this line declares a void-returning function + currentMethod = matches[1] // Extract the method name + } + + // Check if this line contains align information + if alignMatches := alignRegex.FindStringSubmatch(line); alignMatches != nil && currentMethod != "" { + alignStr := alignMatches[1] + align, err := strconv.Atoi(alignStr) + if err != nil { + // Skip this entry if we can't parse the align as integer + currentMethod = "" + continue + } + results = append(results, methodAlign{ + Method: currentMethod, + Align: align, + }) + currentMethod = "" // Reset for next method + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading file: %w", err) + } + + return results, nil +} diff --git a/src/cmd/cgo/internal/testout/testdata/aligned.go b/src/cmd/cgo/internal/testout/testdata/aligned.go new file mode 100644 index 00000000000000..cea6f2889a0cad --- /dev/null +++ b/src/cmd/cgo/internal/testout/testdata/aligned.go @@ -0,0 +1,63 @@ +// Copyright 2025 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 main + +import "C" + +//export ReturnEmpty +func ReturnEmpty() { + return +} + +//export ReturnOnlyUint8 +func ReturnOnlyUint8() (uint8, uint8, uint8) { + return 1, 2, 3 +} + +//export ReturnOnlyUint16 +func ReturnOnlyUint16() (uint16, uint16, uint16) { + return 1, 2, 3 +} + +//export ReturnOnlyUint32 +func ReturnOnlyUint32() (uint32, uint32, uint32) { + return 1, 2, 3 +} + +//export ReturnOnlyUint64 +func ReturnOnlyUint64() (uint64, uint64, uint64) { + return 1, 2, 3 +} + +//export ReturnOnlyInt +func ReturnOnlyInt() (int, int, int) { + return 1, 2, 3 +} + +//export ReturnOnlyPtr +func ReturnOnlyPtr() (*int, *int, *int) { + a, b, c := 1, 2, 3 + return &a, &b, &c +} + +//export ReturnString +func ReturnString() string { + return "hello" +} + +//export ReturnByteSlice +func ReturnByteSlice() []byte { + return []byte{1, 2, 3} +} + +//export InputAndReturnUint8 +func InputAndReturnUint8(a, b, c uint8) (uint8, uint8, uint8) { + return a, b, c +} + +//export MixedTypes +func MixedTypes(a uint8, b uint16, c uint32, d uint64, e int, f *int) (uint8, uint16, uint32, uint64, int, *int) { + return a, b, c, d, e, f +} diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go index 10870b7c85cebd..046041ab3835ac 100644 --- a/src/cmd/cgo/out.go +++ b/src/cmd/cgo/out.go @@ -949,6 +949,8 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) { fmt.Fprintf(gotype, "struct {\n") off := int64(0) npad := 0 + // the align is at least 1 (for char) + maxAlign := int64(1) argField := func(typ ast.Expr, namePat string, args ...interface{}) { name := fmt.Sprintf(namePat, args...) t := p.cgoType(typ) @@ -963,6 +965,11 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) { noSourceConf.Fprint(gotype, fset, typ) fmt.Fprintf(gotype, "\n") off += t.Size + // keep track of the maximum alignment among all fields + // so that we can align the struct correctly + if t.Align > maxAlign { + maxAlign = t.Align + } } if fn.Recv != nil { argField(fn.Recv.List[0].Type, "recv") @@ -1051,7 +1058,11 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) { // string.h for memset, and is also robust to C++ // types with constructors. Both GCC and LLVM optimize // this into just zeroing _cgo_a. - fmt.Fprintf(fgcc, "\ttypedef %s %v _cgo_argtype;\n", ctype.String(), p.packedAttribute()) + // + // The struct should be aligned to the maximum alignment + // of any of its fields. This to avoid alignment + // issues. + fmt.Fprintf(fgcc, "\ttypedef %s %v __attribute__((aligned(%d))) _cgo_argtype;\n", ctype.String(), p.packedAttribute(), maxAlign) fmt.Fprintf(fgcc, "\tstatic _cgo_argtype _cgo_zero;\n") fmt.Fprintf(fgcc, "\t_cgo_argtype _cgo_a = _cgo_zero;\n") if gccResult != "void" && (len(fntype.Results.List) > 1 || len(fntype.Results.List[0].Names) > 1) { From 9c9e473310dd3ad0f1b1a5e2abe78527a74cc711 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Thu, 18 Dec 2025 10:09:12 -0500 Subject: [PATCH 59/59] version --- VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 9e2157943c8db8..e438c586cadc3e 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -go1.25.5 -time 2025-11-26T02:01:51Z +go1.25.5-kb +time 2025-12-18T10:09:51Z