From f6db7350e1287c1d3570b182756cc75f5c7789fe Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Wed, 8 Oct 2025 17:13:12 -0700 Subject: [PATCH 01/15] [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 02/15] [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 03/15] [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 04/15] [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 05/15] [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 06/15] [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 07/15] [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 08/15] [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 09/15] [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 10/15] [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 11/15] [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 12/15] [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 13/15] [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 14/15] [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 15/15] [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