From 0b4d5f85e68ea79b61de9989f9f37a984eb11289 Mon Sep 17 00:00:00 2001 From: Dimitri John Ledkov Date: Thu, 29 Jan 2026 05:30:42 +0100 Subject: [PATCH 01/31] [release-branch.go1.26] cmd/link: use bfd ld 2.36+ on linux/arm64 instead of gold The bfd linker has been fixed for a while. In the mean time gold got deprecated and has stopped receiving new features. Add runtime version checking and only use gold, if bfd ld 2.35 and lower is detected. This enables using `-buildmode=shared` on arm64 without installing binutils-gold (on distributions that split package this), as well as to use external ldflags that ld.bfd supports, and ld.gold does not. For example, this enables to specify gcs-report-dynamic=none when building with GCC-15. For #22040. Fixes #78406. Change-Id: I4eb8b3dabb78844ff662332ad63a4625278271b1 Cq-Include-Trybots: luci.golang.try:go1.26-linux-arm64_debian13 Reviewed-on: https://go-review.googlesource.com/c/go/+/740480 Reviewed-by: Cherry Mui Reviewed-by: Mark Freeman LUCI-TryBot-Result: Go LUCI Auto-Submit: Cherry Mui Reviewed-on: https://go-review.googlesource.com/c/go/+/760302 Reviewed-by: Dmitri Shuralyov Auto-Submit: Dmitri Shuralyov LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/cmd/link/internal/ld/lib.go | 46 ++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index bcad5add4abe19..1427f9c7bc62dc 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1700,22 +1700,48 @@ func (ctxt *Link) hostlink() { } if ctxt.Arch.InFamily(sys.ARM64) && buildcfg.GOOS == "linux" { - // On ARM64, the GNU linker will fail with - // -znocopyreloc if it thinks a COPY relocation is - // required. Switch to gold. + // On ARM64, the GNU linker had issues with -znocopyreloc + // and COPY relocations. This was fixed in GNU ld 2.36+. // https://sourceware.org/bugzilla/show_bug.cgi?id=19962 // https://go.dev/issue/22040 - altLinker = "gold" + // And newer gold is deprecated, may lack new features/flags, or even missing - // If gold is not installed, gcc will silently switch - // back to ld.bfd. So we parse the version information - // and provide a useful error if gold is missing. + // If the default linker is GNU ld 2.35 or older, use gold + useGold := false name, args := flagExtld[0], flagExtld[1:] - args = append(args, "-fuse-ld=gold", "-Wl,--version") + args = append(args, "-Wl,--version") cmd := exec.Command(name, args...) if out, err := cmd.CombinedOutput(); err == nil { - if !bytes.Contains(out, []byte("GNU gold")) { - log.Fatalf("ARM64 external linker must be gold (issue #15696, 22040), but is not: %s", out) + // Parse version from output like "GNU ld (GNU Binutils for Distro) 2.36.1" + for line := range strings.Lines(string(out)) { + if !strings.HasPrefix(line, "GNU ld ") { + continue + } + fields := strings.Fields(line[len("GNU ld "):]) + var major, minor int + if ret, err := fmt.Sscanf(fields[len(fields)-1], "%d.%d", &major, &minor); ret == 2 && err == nil { + if major == 2 && minor <= 35 { + useGold = true + } + break + } + } + } + + if useGold { + // Use gold for older linkers + altLinker = "gold" + + // If gold is not installed, gcc will silently switch + // back to ld.bfd. So we parse the version information + // and provide a useful error if gold is missing. + args = flagExtld[1:] + args = append(args, "-fuse-ld=gold", "-Wl,--version") + cmd = exec.Command(name, args...) + if out, err := cmd.CombinedOutput(); err == nil { + if !bytes.Contains(out, []byte("GNU gold")) { + log.Fatalf("ARM64 external linker must be ld>=2.36 or gold (issue #15696, 22040), but is not: %s", out) + } } } } From f4e425d342728ee354a01a19af870c81d63a5ac2 Mon Sep 17 00:00:00 2001 From: Mark Freeman Date: Tue, 14 Apr 2026 16:13:06 -0400 Subject: [PATCH 02/31] [release-branch.go1.26] fix incorrect loop trip counts While CL 758801 addresses this fix on tip, it does not apply cleanly on go1.25 or go1.26. In the interest of safety, this disables loop inversion; it's the least invasive path. Fixes #78375 Change-Id: Iac399ca47b811042dc5f38272d201d3dc61390b8 Reviewed-on: https://go-review.googlesource.com/c/go/+/766982 Reviewed-by: Jorropo TryBot-Bypass: Dmitri Shuralyov Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssa/prove.go | 118 -------------------------- 1 file changed, 118 deletions(-) diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go index de16dfb3406eec..27afa8e33a628f 100644 --- a/src/cmd/compile/internal/ssa/prove.go +++ b/src/cmd/compile/internal/ssa/prove.go @@ -1517,124 +1517,6 @@ func prove(f *Func) { // Since this induction variable is not used for anything but counting the iterations, // no point in putting it into the facts table. } - - // try to rewrite to a downward counting loop checking against start if the - // loop body does not depend on ind or nxt and end is known before the loop. - // This reduces pressure on the register allocator because this does not need - // to use end on each iteration anymore. We compare against the start constant instead. - // That means this code: - // - // loop: - // ind = (Phi (Const [x]) nxt), - // if ind < end - // then goto enter_loop - // else goto exit_loop - // - // enter_loop: - // do something without using ind nor nxt - // nxt = inc + ind - // goto loop - // - // exit_loop: - // - // is rewritten to: - // - // loop: - // ind = (Phi end nxt) - // if (Const [x]) < ind - // then goto enter_loop - // else goto exit_loop - // - // enter_loop: - // do something without using ind nor nxt - // nxt = ind - inc - // goto loop - // - // exit_loop: - // - // this is better because it only requires to keep ind then nxt alive while looping, - // while the original form keeps ind then nxt and end alive - start, end := v.min, v.max - if v.flags&indVarCountDown != 0 { - start, end = end, start - } - - if !start.isGenericIntConst() { - // if start is not a constant we would be winning nothing from inverting the loop - continue - } - if end.isGenericIntConst() { - // TODO: if both start and end are constants we should rewrite such that the comparison - // is against zero and nxt is ++ or -- operation - // That means: - // for i := 2; i < 11; i += 2 { - // should be rewritten to: - // for i := 5; 0 < i; i-- { - continue - } - - if end.Block == ind.Block { - // we can't rewrite loops where the condition depends on the loop body - // this simple check is forced to work because if this is true a Phi in ind.Block must exist - continue - } - - check := ind.Block.Controls[0] - // invert the check - check.Args[0], check.Args[1] = check.Args[1], check.Args[0] - - // swap start and end in the loop - for i, v := range check.Args { - if v != end { - continue - } - - check.SetArg(i, start) - goto replacedEnd - } - panic(fmt.Sprintf("unreachable, ind: %v, start: %v, end: %v", ind, start, end)) - replacedEnd: - - for i, v := range ind.Args { - if v != start { - continue - } - - ind.SetArg(i, end) - goto replacedStart - } - panic(fmt.Sprintf("unreachable, ind: %v, start: %v, end: %v", ind, start, end)) - replacedStart: - - if nxt.Args[0] != ind { - // unlike additions subtractions are not commutative so be sure we get it right - nxt.Args[0], nxt.Args[1] = nxt.Args[1], nxt.Args[0] - } - - switch nxt.Op { - case OpAdd8: - nxt.Op = OpSub8 - case OpAdd16: - nxt.Op = OpSub16 - case OpAdd32: - nxt.Op = OpSub32 - case OpAdd64: - nxt.Op = OpSub64 - case OpSub8: - nxt.Op = OpAdd8 - case OpSub16: - nxt.Op = OpAdd16 - case OpSub32: - nxt.Op = OpAdd32 - case OpSub64: - nxt.Op = OpAdd64 - default: - panic("unreachable") - } - - if f.pass.debug > 0 { - f.Warnl(ind.Pos, "Inverted loop iteration") - } } ft := newFactsTable(f) From ba4554f03b0dbd0c6fa389be6edc2ae399bd5f9c Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Tue, 17 Mar 2026 17:07:02 -0400 Subject: [PATCH 03/31] [release-branch.go1.26] cmd/go: specify full path to go command when running go tool covdata Otherwise the GOROOT will be a post-1.25 GOROOT, while we try to run "go tool covdata" with a go command that's 1.24 or earlier from the post 1.25 toolchain. The 1.24 go command won't be able to find covdata in the 1.25 goroot because go 1.25 and later don't ship with a prebuilt covdata tool. For #71867 For #75031 Fixes #78412 Change-Id: I770f10a288347ac33cf721d34a2adb1a6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/756220 Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob Reviewed-by: Dmitri Shuralyov (cherry picked from commit 90adad7b2565d456bf5e120a59a07ff31f3ada45) Reviewed-on: https://go-review.googlesource.com/c/go/+/760500 TryBot-Bypass: Dmitri Shuralyov --- src/cmd/go/internal/work/cover.go | 2 +- .../go/testdata/script/cover_switch_toolchain.txt | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/cmd/go/testdata/script/cover_switch_toolchain.txt diff --git a/src/cmd/go/internal/work/cover.go b/src/cmd/go/internal/work/cover.go index fc96f67d6e8a0b..be090ce4c3b50b 100644 --- a/src/cmd/go/internal/work/cover.go +++ b/src/cmd/go/internal/work/cover.go @@ -24,7 +24,7 @@ import ( func (b *Builder) CovData(a *Action, cmdargs ...any) ([]byte, error) { cmdline := str.StringList(cmdargs...) args := append([]string{}, cfg.BuildToolexec...) - args = append(args, "go", "tool", "covdata") + args = append(args, filepath.Join(cfg.GOROOTbin, "go"), "tool", "covdata") args = append(args, cmdline...) return b.Shell(a).runOut(a.Objdir, nil, args) } diff --git a/src/cmd/go/testdata/script/cover_switch_toolchain.txt b/src/cmd/go/testdata/script/cover_switch_toolchain.txt new file mode 100644 index 00000000000000..dace804fb6cb27 --- /dev/null +++ b/src/cmd/go/testdata/script/cover_switch_toolchain.txt @@ -0,0 +1,12 @@ +go test -cover -n ./... +[!GOOS:windows] stderr $GOROOT'/bin/go tool covdata' +[GOOS:windows] stderr '\\\\bin\\\\go" tool covdata' + +-- go.mod -- +module example.com/m + +go 1.25.0 +-- m.go -- +package main + +func main() {} From efdc0fb3545f7f98c6e8f391a6e96c53b0aeb7c5 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 13 Apr 2026 15:53:10 +0700 Subject: [PATCH 04/31] [release-branch.go1.26] cmd/compile: handle min integer step in loop Since negating min int will overflows back to itself, causing a panic inside subWillUnderflow check. Fixes #78676 Change-Id: Ibbf2fa3228b9890a1a76ac6f4ff504b7e125b29f Reviewed-on: https://go-review.googlesource.com/c/go/+/766260 Auto-Submit: Cuong Manh Le LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: David Chase Reviewed-by: Jorropo Reviewed-by: Keith Randall Reviewed-on: https://go-review.googlesource.com/c/go/+/766840 TryBot-Bypass: Cuong Manh Le --- src/cmd/compile/internal/ssa/loopbce.go | 6 ++++++ test/fixedbugs/issue78641.go | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 test/fixedbugs/issue78641.go diff --git a/src/cmd/compile/internal/ssa/loopbce.go b/src/cmd/compile/internal/ssa/loopbce.go index a63a314043fe89..84811647cedcd1 100644 --- a/src/cmd/compile/internal/ssa/loopbce.go +++ b/src/cmd/compile/internal/ssa/loopbce.go @@ -145,6 +145,12 @@ func findIndVar(f *Func) []indVar { if step == 0 { continue } + // step == minInt64 cannot be safely negated below, because -step + // overflows back to minInt64. The later underflow checks need a + // positive magnitude, so reject this case here. + if step == minSignedValue(ind.Type) { + continue + } // startBody is the edge that eventually returns to the loop header. var startBody Edge diff --git a/test/fixedbugs/issue78641.go b/test/fixedbugs/issue78641.go new file mode 100644 index 00000000000000..be964354af1ee5 --- /dev/null +++ b/test/fixedbugs/issue78641.go @@ -0,0 +1,25 @@ +// run + +// Copyright 2026 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 + +const ( + intSize = 32 << (^uint(0) >> 63) + minInt = -1 << (intSize - 1) +) + +func main() { + f() +} + +func f() { + for i := 0; true; i += minInt { + if i < 0 { + return + } + } + panic("unreachable") +} From 710f29a7582165978a39fd730d711e6de8702ff9 Mon Sep 17 00:00:00 2001 From: Michael Pratt Date: Thu, 2 Apr 2026 13:17:23 -0400 Subject: [PATCH 05/31] [release-branch.go1.26] runtime: add sysUnreserve to undo sysReserve This is inspired by CL 724560 by Bobby Powers, particularly their great commit message. When using address sanitizer with leak detection, sysReserve registers memory regions with LSAN via lsanregisterrootregion. However, several code paths release this memory using sysFreeOS without first unregistering from LSAN. This leaves LSAN with stale root region entries pointing to memory that has been unmapped and may be reallocated for other purposes. This bug was latent until glibc 2.42, which changed pthread stack guard pages from mprotect(PROT_NONE) to madvise(MADV_GUARD_INSTALL). The difference matters because LSAN filters root region scanning by intersecting registered regions with readable mappings from /proc/self/maps: - mprotect(PROT_NONE) splits the VMA, creating a separate entry with ---p permissions. LSAN's IsReadable() check excludes it from scanning. - MADV_GUARD_INSTALL operates at the page table level without modifying the VMA. The region still appears as rw-p in /proc/self/maps, so LSAN includes it in the scan and crashes with SIGSEGV when accessing the guard pages. Address this by adding sysUnreserve to undo sysReserve. sysUnreserve unregisters the region from LSAN and frees the mapping. With the addition of sysUnreserve, we have complete coverage of LSAN unregister in the mem.go abstract: sysFree unregisters Ready memory. sysUnreserve unregisters Reserved memory. And there is no way to free Prepared memory at all (it must transition to Ready or Reserved first). The implementation of lsanunregisterrootregion [1] finds the region by exact match of start and end address. It therefore does not support splitting a region, and we must extend this requirement to sysUnreserve and sysFree. I am not completely confident that we always pass the full region to sysFree, but LSAN aborts if it can't find the region, so we must not be blatantly violating this. sysReserveAligned does need to unreserve a subset of a region, so it cannot use sysUnreserve directly. Rather than breaking the mem.go abstract, move sysReserveAligned into mem.go, adding it to the abstraction. We should not have any calls to sysFreeOS outside of the mem.go abstraction. That is now true with this CL. Fixes #78511. [1] https://github.com/llvm/llvm-project/blob/3e3e362648fa062038b90ccc21f46a09d6902288/compiler-rt/lib/lsan/lsan_common.cpp#L1157 Change-Id: I8c46a62154b2f23456ffd5086a7b91156a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/762381 Reviewed-by: Michael Knyszek Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI (cherry picked from commit 40ec033c33802cf6e1236ea8030d882338a457d5) Reviewed-on: https://go-review.googlesource.com/c/go/+/767022 TryBot-Bypass: Carlos Amedee Reviewed-by: Carlos Amedee --- src/runtime/export_test.go | 20 +++++-- src/runtime/malloc.go | 54 +----------------- src/runtime/mem.go | 113 ++++++++++++++++++++++++++++++++++--- 3 files changed, 119 insertions(+), 68 deletions(-) diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go index 95e2cbb95929d1..15365bff38f326 100644 --- a/src/runtime/export_test.go +++ b/src/runtime/export_test.go @@ -546,7 +546,7 @@ func MapNextArenaHint() (start, end uintptr, ok bool) { if !ok { // We were unable to get the requested reservation. // Release what we did get and fail. - sysFreeOS(got, physPageSize) + sysUnreserve(got, physPageSize) } return } @@ -1087,19 +1087,21 @@ func FreePageAlloc(pp *PageAlloc) { // Free all the mapped space for the summary levels. if pageAlloc64Bit != 0 { for l := 0; l < summaryLevels; l++ { - sysFreeOS(unsafe.Pointer(&p.summary[l][0]), uintptr(cap(p.summary[l]))*pallocSumBytes) + // This isn't quite right, as some of this memory may + // be Ready instead of Reserved. The mappedReady and + // testSysStat adjustments below correct for the + // difference. + sysUnreserve(unsafe.Pointer(&p.summary[l][0]), uintptr(cap(p.summary[l]))*pallocSumBytes) } } else { resSize := uintptr(0) for _, s := range p.summary { resSize += uintptr(cap(s)) * pallocSumBytes } - sysFreeOS(unsafe.Pointer(&p.summary[0][0]), alignUp(resSize, physPageSize)) + // See sysUnreserve comment above. + sysUnreserve(unsafe.Pointer(&p.summary[0][0]), alignUp(resSize, physPageSize)) } - // Free extra data structures. - sysFreeOS(unsafe.Pointer(&p.scav.index.chunks[0]), uintptr(cap(p.scav.index.chunks))*unsafe.Sizeof(atomicScavChunkData{})) - // Subtract back out whatever we mapped for the summaries. // sysUsed adds to p.sysStat and memstats.mappedReady no matter what // (and in anger should actually be accounted for), and there's no other @@ -1107,6 +1109,12 @@ func FreePageAlloc(pp *PageAlloc) { gcController.mappedReady.Add(-int64(p.summaryMappedReady)) testSysStat.add(-int64(p.summaryMappedReady)) + // Free extra data structures. + // + // TODO(prattmic): As above, some of this may be Ready, so we should + // manually adjust mappedReady and testSysStat? + sysUnreserve(unsafe.Pointer(&p.scav.index.chunks[0]), uintptr(cap(p.scav.index.chunks))*unsafe.Sizeof(atomicScavChunkData{})) + // Free the mapped space for chunks. for i := range p.chunks { if x := p.chunks[i]; x != nil { diff --git a/src/runtime/malloc.go b/src/runtime/malloc.go index c08bc7574ba411..6a4ee5bd42007c 100644 --- a/src/runtime/malloc.go +++ b/src/runtime/malloc.go @@ -788,7 +788,7 @@ func (h *mheap) sysAlloc(n uintptr, hintList **arenaHint, arenaList *[]arenaIdx) // particular, this is already how Windows behaves, so // it would simplify things there. if v != nil { - sysFreeOS(v, n) + sysUnreserve(v, n) } *hintList = hint.next h.arenaHintAlloc.free(unsafe.Pointer(hint)) @@ -921,58 +921,6 @@ mapped: return } -// sysReserveAligned is like sysReserve, but the returned pointer is -// aligned to align bytes. It may reserve either n or n+align bytes, -// so it returns the size that was reserved. -func sysReserveAligned(v unsafe.Pointer, size, align uintptr, vmaName string) (unsafe.Pointer, uintptr) { - if isSbrkPlatform { - if v != nil { - throw("unexpected heap arena hint on sbrk platform") - } - return sysReserveAlignedSbrk(size, align) - } - // Since the alignment is rather large in uses of this - // function, we're not likely to get it by chance, so we ask - // for a larger region and remove the parts we don't need. - retries := 0 -retry: - p := uintptr(sysReserve(v, size+align, vmaName)) - switch { - case p == 0: - return nil, 0 - case p&(align-1) == 0: - return unsafe.Pointer(p), size + align - case GOOS == "windows": - // On Windows we can't release pieces of a - // reservation, so we release the whole thing and - // re-reserve the aligned sub-region. This may race, - // so we may have to try again. - sysFreeOS(unsafe.Pointer(p), size+align) - p = alignUp(p, align) - p2 := sysReserve(unsafe.Pointer(p), size, vmaName) - if p != uintptr(p2) { - // Must have raced. Try again. - sysFreeOS(p2, size) - if retries++; retries == 100 { - throw("failed to allocate aligned heap memory; too many retries") - } - goto retry - } - // Success. - return p2, size - default: - // Trim off the unaligned parts. - pAligned := alignUp(p, align) - sysFreeOS(unsafe.Pointer(p), pAligned-p) - end := pAligned + size - endLen := (p + size + align) - end - if endLen > 0 { - sysFreeOS(unsafe.Pointer(end), endLen) - } - return unsafe.Pointer(pAligned), size - } -} - // enableMetadataHugePages enables huge pages for various sources of heap metadata. // // A note on latency: for sufficiently small heaps (<10s of GiB) this function will take constant diff --git a/src/runtime/mem.go b/src/runtime/mem.go index f373173eb36fc1..1a12309ca59dfc 100644 --- a/src/runtime/mem.go +++ b/src/runtime/mem.go @@ -120,14 +120,21 @@ func sysHugePageCollapse(v unsafe.Pointer, n uintptr) { // // sysStat must be non-nil. // +// The size and start address must exactly match the size and returned address +// from the original sysAlloc/sysReserve/sysReserveAligned call. That is, +// sysFree cannot be used to free a subset of a memory region. +// // Don't split the stack as this function may be invoked without a valid G, // which prevents us from allocating more stack. // //go:nosplit func sysFree(v unsafe.Pointer, n uintptr, sysStat *sysMemStat) { - // When using ASAN leak detection, the memory being freed is - // known by the sanitizer. We need to unregister it so it's - // not accessed by it. + // When using ASAN leak detection, the memory being freed is known by + // the sanitizer. We need to unregister it so it's not accessed by it. + // + // lsanunregisterrootregion matches regions by start address and size, + // so it is not possible to unregister a subset of the region. This is + // why sysFree requires the full region from the initial allocation. if asanenabled { lsanunregisterrootregion(v, n) } @@ -156,13 +163,12 @@ func sysFault(v unsafe.Pointer, n uintptr) { // (either via permissions or not committing the memory). Such a reservation is // thus never backed by physical memory. // -// If the pointer passed to it is non-nil, the caller wants the -// reservation there, but sysReserve can still choose another -// location if that one is unavailable. +// If the pointer passed to it is non-nil, the caller wants the reservation +// there, but sysReserve can still choose another location if that one is +// unavailable. // -// NOTE: sysReserve returns OS-aligned memory, but the heap allocator -// may use larger alignment, so the caller must be careful to realign the -// memory obtained by sysReserve. +// sysReserve returns OS-aligned memory. If a larger alignment is required, use +// sysReservedAligned. func sysReserve(v unsafe.Pointer, n uintptr, vmaName string) unsafe.Pointer { p := sysReserveOS(v, n, vmaName) @@ -175,6 +181,95 @@ func sysReserve(v unsafe.Pointer, n uintptr, vmaName string) unsafe.Pointer { return p } +// sysReserveAligned transitions a memory region from None to Reserved. +// +// Semantics are equivlent to sysReserve, but the returned pointer is aligned +// to align bytes. It may reserve either n or n+align bytes, so it returns the +// size that was reserved. +func sysReserveAligned(v unsafe.Pointer, size, align uintptr, vmaName string) (unsafe.Pointer, uintptr) { + if isSbrkPlatform { + if v != nil { + throw("unexpected heap arena hint on sbrk platform") + } + return sysReserveAlignedSbrk(size, align) + } + // Since the alignment is rather large in uses of this + // function, we're not likely to get it by chance, so we ask + // for a larger region and remove the parts we don't need. + retries := 0 +retry: + p := uintptr(sysReserve(v, size+align, vmaName)) + switch { + case p == 0: + return nil, 0 + case p&(align-1) == 0: + return unsafe.Pointer(p), size + align + case GOOS == "windows": + // On Windows we can't release pieces of a + // reservation, so we release the whole thing and + // re-reserve the aligned sub-region. This may race, + // so we may have to try again. + sysUnreserve(unsafe.Pointer(p), size+align) + p = alignUp(p, align) + p2 := sysReserve(unsafe.Pointer(p), size, vmaName) + if p != uintptr(p2) { + // Must have raced. Try again. + sysUnreserve(p2, size) + if retries++; retries == 100 { + throw("failed to allocate aligned heap memory; too many retries") + } + goto retry + } + // Success. + return p2, size + default: + // Trim off the unaligned parts. + pAligned := alignUp(p, align) + end := pAligned + size + endLen := (p + size + align) - end + + // sysUnreserve does not allow unreserving a subset of the + // region because LSAN does not allow unregistering a subset. + // So we can't call sysUnreserve. Instead we simply unregister + // the entire region from LSAN and re-register with the smaller + // region before freeing the unecessary portions, which does + // allow subsets of the region. + if asanenabled { + lsanunregisterrootregion(unsafe.Pointer(p), size+align) + lsanregisterrootregion(unsafe.Pointer(pAligned), size) + } + sysFreeOS(unsafe.Pointer(p), pAligned-p) + if endLen > 0 { + sysFreeOS(unsafe.Pointer(end), endLen) + } + return unsafe.Pointer(pAligned), size + } +} + +// sysUnreserve transitions a memory region from Reserved to None. +// +// The size and start address must exactly match the size and returned address +// from sysReserve/sysReserveAligned. That is, sysUnreserve cannot be used to +// unreserve a subset of a memory region. +// +// Don't split the stack as this function may be invoked without a valid G, +// which prevents us from allocating more stack. +// +//go:nosplit +func sysUnreserve(v unsafe.Pointer, n uintptr) { + // When using ASAN leak detection, the memory being freed is known by + // the sanitizer. We need to unregister it so it's not accessed by it. + // + // lsanunregisterrootregion matches regions by start address and size, + // so it is not possible to unregister a subset of the region. This is + // why sysUnreserve requires the full region from sysReserve. + if asanenabled { + lsanunregisterrootregion(v, n) + } + + sysFreeOS(v, n) +} + // sysMap transitions a memory region from Reserved to Prepared. It ensures the // memory region can be efficiently transitioned to Ready. // From ec5ebece414661dd9341d8b06afda3fa71545c2f Mon Sep 17 00:00:00 2001 From: Mark Freeman Date: Fri, 17 Apr 2026 15:59:51 -0400 Subject: [PATCH 06/31] [release-branch.go1.26] all: update x/net to 705de46f Fixes #78478 Change-Id: Ic950951a8149a9db0c43e7f6846926b2806a8889 Reviewed-on: https://go-review.googlesource.com/c/go/+/768500 Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/go.mod | 2 +- src/go.sum | 4 ++-- src/net/http/h2_bundle.go | 6 +++--- src/vendor/modules.txt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/go.mod b/src/go.mod index efc07451b53448..b329ba18def47c 100644 --- a/src/go.mod +++ b/src/go.mod @@ -4,7 +4,7 @@ go 1.26 require ( golang.org/x/crypto v0.46.1-0.20251210140736-7dacc380ba00 - golang.org/x/net v0.47.1-0.20251128220604-7c360367ab7e + golang.org/x/net v0.47.1-0.20260417193450-705de46f8788 ) require ( diff --git a/src/go.sum b/src/go.sum index b6b841b44d8e38..37f908522c1926 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,7 +1,7 @@ golang.org/x/crypto v0.46.1-0.20251210140736-7dacc380ba00 h1:JgcPM1rzpSOZS8y69FQvnY0xN0ciHlpQqwTXJcuZIA4= golang.org/x/crypto v0.46.1-0.20251210140736-7dacc380ba00/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/net v0.47.1-0.20251128220604-7c360367ab7e h1:PAAT9cIDvIAIRQVz2txQvUFRt3jOlhiO84ihd8XMGlg= -golang.org/x/net v0.47.1-0.20251128220604-7c360367ab7e/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.47.1-0.20260417193450-705de46f8788 h1:fVWwoa/P68Bsajqy2FO4dha7TRBfgf09o932L4USeXI= +golang.org/x/net v0.47.1-0.20260417193450-705de46f8788/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= diff --git a/src/net/http/h2_bundle.go b/src/net/http/h2_bundle.go index 5a7420bc7e12ba..c440e76acee6d1 100644 --- a/src/net/http/h2_bundle.go +++ b/src/net/http/h2_bundle.go @@ -10152,6 +10152,9 @@ func (rl *http2clientConnReadLoop) processSettingsNoWrite(f *http2SettingsFrame) var seenMaxConcurrentStreams bool err := f.ForeachSetting(func(s http2Setting) error { + if err := s.Valid(); err != nil { + return err + } switch s.ID { case http2SettingMaxFrameSize: cc.maxFrameSize = s.Val @@ -10183,9 +10186,6 @@ func (rl *http2clientConnReadLoop) processSettingsNoWrite(f *http2SettingsFrame) cc.henc.SetMaxDynamicTableSize(s.Val) cc.peerMaxHeaderTableSize = s.Val case http2SettingEnableConnectProtocol: - if err := s.Valid(); err != nil { - return err - } // If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL, // we require that it do so in the first SETTINGS frame. // diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index b6f6376eac041a..ceaebeb8a55620 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -6,7 +6,7 @@ golang.org/x/crypto/cryptobyte golang.org/x/crypto/cryptobyte/asn1 golang.org/x/crypto/internal/alias golang.org/x/crypto/internal/poly1305 -# golang.org/x/net v0.47.1-0.20251128220604-7c360367ab7e +# golang.org/x/net v0.47.1-0.20260417193450-705de46f8788 ## explicit; go 1.24.0 golang.org/x/net/dns/dnsmessage golang.org/x/net/http/httpguts From be12fe151cf3866fd61a55a631e30c8f312f48b7 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 24 Mar 2026 23:02:09 +0000 Subject: [PATCH 07/31] [release-branch.go1.26] runtime: use uname version check for 64-bit time on 32-bit arch codepaths The previous fallback-on-ENOSYS logic causes issues on forks of Linux. Android: #77621 (CL 750040 added a workaround with a TODO, this fixes that TODO) Causes the OS to terminate the program when running on Android versions <=10 since the seccomp jail does not know about the 64-bit time syscall and is configured to terminate the program on any unknown syscall. Synology's Linux: #77930 On old versions of Synology's Linux they added custom vendor syscalls without adding a gap in the syscall numbers, that means when we call the newer Linux syscall which was added later, Synology's Linux interprets it as a completely different vendor syscall. Originally by Jorropo in CL 751340. Updates #77930 Fixes #77931 Co-authored-by: Jorropo Change-Id: I90e15495d9249fd7f6e112f9e3ae8ad1322f56e0 Reviewed-on: https://go-review.googlesource.com/c/go/+/758902 Reviewed-by: Damien Neil Reviewed-by: Michael Pratt Reviewed-by: Jorropo LUCI-TryBot-Result: Go LUCI (cherry picked from commit 04dc12c1a17d3fa4ff49af84de5641099716e234) Reviewed-on: https://go-review.googlesource.com/c/go/+/770220 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- .../runtime/syscall/linux/defs_linux_386.go | 1 + .../runtime/syscall/linux/defs_linux_amd64.go | 1 + .../runtime/syscall/linux/defs_linux_arm.go | 1 + .../runtime/syscall/linux/defs_linux_arm64.go | 1 + .../syscall/linux/defs_linux_loong64.go | 1 + .../syscall/linux/defs_linux_mips64x.go | 1 + .../runtime/syscall/linux/defs_linux_mipsx.go | 1 + .../syscall/linux/defs_linux_ppc64x.go | 1 + .../syscall/linux/defs_linux_riscv64.go | 1 + .../runtime/syscall/linux/defs_linux_s390x.go | 1 + .../runtime/syscall/linux/syscall_linux.go | 14 +++++ src/runtime/os_linux.go | 62 +++++++++++++++++++ src/runtime/os_linux32.go | 28 +++------ src/runtime/os_linux64.go | 2 + 14 files changed, 97 insertions(+), 19 deletions(-) diff --git a/src/internal/runtime/syscall/linux/defs_linux_386.go b/src/internal/runtime/syscall/linux/defs_linux_386.go index 7fdf5d3f8062fa..4e8e645dc49a66 100644 --- a/src/internal/runtime/syscall/linux/defs_linux_386.go +++ b/src/internal/runtime/syscall/linux/defs_linux_386.go @@ -17,6 +17,7 @@ const ( SYS_OPENAT = 295 SYS_PREAD64 = 180 SYS_READ = 3 + SYS_UNAME = 122 EFD_NONBLOCK = 0x800 diff --git a/src/internal/runtime/syscall/linux/defs_linux_amd64.go b/src/internal/runtime/syscall/linux/defs_linux_amd64.go index 2c8676e6e9b4d9..fa764d9ccd9b8e 100644 --- a/src/internal/runtime/syscall/linux/defs_linux_amd64.go +++ b/src/internal/runtime/syscall/linux/defs_linux_amd64.go @@ -17,6 +17,7 @@ const ( SYS_OPENAT = 257 SYS_PREAD64 = 17 SYS_READ = 0 + SYS_UNAME = 63 EFD_NONBLOCK = 0x800 diff --git a/src/internal/runtime/syscall/linux/defs_linux_arm.go b/src/internal/runtime/syscall/linux/defs_linux_arm.go index a0b395d6762734..cef556d5f6f986 100644 --- a/src/internal/runtime/syscall/linux/defs_linux_arm.go +++ b/src/internal/runtime/syscall/linux/defs_linux_arm.go @@ -17,6 +17,7 @@ const ( SYS_OPENAT = 322 SYS_PREAD64 = 180 SYS_READ = 3 + SYS_UNAME = 122 EFD_NONBLOCK = 0x800 diff --git a/src/internal/runtime/syscall/linux/defs_linux_arm64.go b/src/internal/runtime/syscall/linux/defs_linux_arm64.go index 223dce0c5b4281..eabddbac1bc063 100644 --- a/src/internal/runtime/syscall/linux/defs_linux_arm64.go +++ b/src/internal/runtime/syscall/linux/defs_linux_arm64.go @@ -17,6 +17,7 @@ const ( SYS_OPENAT = 56 SYS_PREAD64 = 67 SYS_READ = 63 + SYS_UNAME = 160 EFD_NONBLOCK = 0x800 diff --git a/src/internal/runtime/syscall/linux/defs_linux_loong64.go b/src/internal/runtime/syscall/linux/defs_linux_loong64.go index 8aa61c391dcdcb..08e5d49b83c9bd 100644 --- a/src/internal/runtime/syscall/linux/defs_linux_loong64.go +++ b/src/internal/runtime/syscall/linux/defs_linux_loong64.go @@ -17,6 +17,7 @@ const ( SYS_OPENAT = 56 SYS_PREAD64 = 67 SYS_READ = 63 + SYS_UNAME = 160 EFD_NONBLOCK = 0x800 diff --git a/src/internal/runtime/syscall/linux/defs_linux_mips64x.go b/src/internal/runtime/syscall/linux/defs_linux_mips64x.go index 84b760dc1b5545..b5794e5002af5e 100644 --- a/src/internal/runtime/syscall/linux/defs_linux_mips64x.go +++ b/src/internal/runtime/syscall/linux/defs_linux_mips64x.go @@ -19,6 +19,7 @@ const ( SYS_OPENAT = 5247 SYS_PREAD64 = 5016 SYS_READ = 5000 + SYS_UNAME = 5061 EFD_NONBLOCK = 0x80 diff --git a/src/internal/runtime/syscall/linux/defs_linux_mipsx.go b/src/internal/runtime/syscall/linux/defs_linux_mipsx.go index a9be21414c26f9..1fb4d919d1a318 100644 --- a/src/internal/runtime/syscall/linux/defs_linux_mipsx.go +++ b/src/internal/runtime/syscall/linux/defs_linux_mipsx.go @@ -19,6 +19,7 @@ const ( SYS_OPENAT = 4288 SYS_PREAD64 = 4200 SYS_READ = 4003 + SYS_UNAME = 4122 EFD_NONBLOCK = 0x80 diff --git a/src/internal/runtime/syscall/linux/defs_linux_ppc64x.go b/src/internal/runtime/syscall/linux/defs_linux_ppc64x.go index 63f4e5d7864de4..ee93ad345b810f 100644 --- a/src/internal/runtime/syscall/linux/defs_linux_ppc64x.go +++ b/src/internal/runtime/syscall/linux/defs_linux_ppc64x.go @@ -19,6 +19,7 @@ const ( SYS_OPENAT = 286 SYS_PREAD64 = 179 SYS_READ = 3 + SYS_UNAME = 122 EFD_NONBLOCK = 0x800 diff --git a/src/internal/runtime/syscall/linux/defs_linux_riscv64.go b/src/internal/runtime/syscall/linux/defs_linux_riscv64.go index 8aa61c391dcdcb..08e5d49b83c9bd 100644 --- a/src/internal/runtime/syscall/linux/defs_linux_riscv64.go +++ b/src/internal/runtime/syscall/linux/defs_linux_riscv64.go @@ -17,6 +17,7 @@ const ( SYS_OPENAT = 56 SYS_PREAD64 = 67 SYS_READ = 63 + SYS_UNAME = 160 EFD_NONBLOCK = 0x800 diff --git a/src/internal/runtime/syscall/linux/defs_linux_s390x.go b/src/internal/runtime/syscall/linux/defs_linux_s390x.go index 52945db0e5b72f..da11c704081abc 100644 --- a/src/internal/runtime/syscall/linux/defs_linux_s390x.go +++ b/src/internal/runtime/syscall/linux/defs_linux_s390x.go @@ -17,6 +17,7 @@ const ( SYS_OPENAT = 288 SYS_PREAD64 = 180 SYS_READ = 3 + SYS_UNAME = 122 EFD_NONBLOCK = 0x800 diff --git a/src/internal/runtime/syscall/linux/syscall_linux.go b/src/internal/runtime/syscall/linux/syscall_linux.go index 8201e7d1907444..b64f511b03c947 100644 --- a/src/internal/runtime/syscall/linux/syscall_linux.go +++ b/src/internal/runtime/syscall/linux/syscall_linux.go @@ -86,3 +86,17 @@ func Pread(fd int, p []byte, offset int64) (n int, errno uintptr) { } return int(r1), e } + +type Utsname struct { + Sysname [65]byte + Nodename [65]byte + Release [65]byte + Version [65]byte + Machine [65]byte + Domainname [65]byte +} + +func Uname(buf *Utsname) (errno uintptr) { + _, _, e := Syscall6(SYS_UNAME, uintptr(unsafe.Pointer(buf)), 0, 0, 0, 0, 0) + return e +} diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 7e6af22d48a764..493567b5303673 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -354,6 +354,7 @@ func osinit() { numCPUStartup = getCPUCount() physHugePageSize = getHugePageSize() vgetrandomInit() + configure64bitsTimeOn32BitsArchitectures() } var urandom_dev = []byte("/dev/urandom\x00") @@ -935,3 +936,64 @@ func mprotect(addr unsafe.Pointer, n uintptr, prot int32) (ret int32, errno int3 r, _, err := linux.Syscall6(linux.SYS_MPROTECT, uintptr(addr), n, uintptr(prot), 0, 0, 0) return int32(r), int32(err) } + +type kernelVersion struct { + major int + minor int +} + +// getKernelVersion returns major and minor kernel version numbers +// parsed from the uname release field. +func getKernelVersion() kernelVersion { + var buf linux.Utsname + if e := linux.Uname(&buf); e != 0 { + throw("uname failed") + } + + rel := gostringnocopy(&buf.Release[0]) + major, minor, _, ok := parseRelease(rel) + if !ok { + throw("failed to parse kernel version from uname") + } + return kernelVersion{major: major, minor: minor} +} + +// parseRelease parses a dot-separated version number. It follows the +// semver syntax, but allows the minor and patch versions to be +// elided. +func parseRelease(rel string) (major, minor, patch int, ok bool) { + // Strip anything after a dash or plus. + for i := 0; i < len(rel); i++ { + if rel[i] == '-' || rel[i] == '+' { + rel = rel[:i] + break + } + } + + next := func() (int, bool) { + for i := 0; i < len(rel); i++ { + if rel[i] == '.' { + ver, err := strconv.Atoi(rel[:i]) + rel = rel[i+1:] + return ver, err == nil + } + } + ver, err := strconv.Atoi(rel) + rel = "" + return ver, err == nil + } + if major, ok = next(); !ok || rel == "" { + return + } + if minor, ok = next(); !ok || rel == "" { + return + } + patch, ok = next() + return +} + +// GE checks if the running kernel version +// is greater than or equal to the provided version. +func (kv kernelVersion) GE(x, y int) bool { + return kv.major > x || (kv.major == x && kv.minor >= y) +} diff --git a/src/runtime/os_linux32.go b/src/runtime/os_linux32.go index 16de6fb350f624..02cb18f32d57d2 100644 --- a/src/runtime/os_linux32.go +++ b/src/runtime/os_linux32.go @@ -7,27 +7,25 @@ package runtime import ( - "internal/runtime/atomic" "unsafe" ) +func configure64bitsTimeOn32BitsArchitectures() { + use64bitsTimeOn32bits = getKernelVersion().GE(5, 1) +} + //go:noescape func futex_time32(addr unsafe.Pointer, op int32, val uint32, ts *timespec32, addr2 unsafe.Pointer, val3 uint32) int32 //go:noescape func futex_time64(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 -var isFutexTime32bitOnly atomic.Bool +var use64bitsTimeOn32bits bool //go:nosplit func futex(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 { - if !isFutexTime32bitOnly.Load() { - ret := futex_time64(addr, op, val, ts, addr2, val3) - // futex_time64 is only supported on Linux 5.0+ - if ret != -_ENOSYS { - return ret - } - isFutexTime32bitOnly.Store(true) + if use64bitsTimeOn32bits { + return futex_time64(addr, op, val, ts, addr2, val3) } // Downgrade ts. var ts32 timespec32 @@ -45,17 +43,10 @@ func timer_settime32(timerid int32, flags int32, new, old *itimerspec32) int32 //go:noescape func timer_settime64(timerid int32, flags int32, new, old *itimerspec) int32 -var isSetTime32bitOnly atomic.Bool - //go:nosplit func timer_settime(timerid int32, flags int32, new, old *itimerspec) int32 { - if !isSetTime32bitOnly.Load() { - ret := timer_settime64(timerid, flags, new, old) - // timer_settime64 is only supported on Linux 5.0+ - if ret != -_ENOSYS { - return ret - } - isSetTime32bitOnly.Store(true) + if use64bitsTimeOn32bits { + return timer_settime64(timerid, flags, new, old) } var newts, oldts itimerspec32 @@ -73,6 +64,5 @@ func timer_settime(timerid int32, flags int32, new, old *itimerspec) int32 { old32 = &oldts } - // Fall back to 32-bit timer return timer_settime32(timerid, flags, new32, old32) } diff --git a/src/runtime/os_linux64.go b/src/runtime/os_linux64.go index 7b70d80fbe5a89..f9571dd7586614 100644 --- a/src/runtime/os_linux64.go +++ b/src/runtime/os_linux64.go @@ -10,6 +10,8 @@ import ( "unsafe" ) +func configure64bitsTimeOn32BitsArchitectures() {} + //go:noescape func futex(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 From 9b01c04815150908b1e6768872fbcdb6cfdea698 Mon Sep 17 00:00:00 2001 From: Neal Patel Date: Mon, 27 Apr 2026 17:34:58 -0400 Subject: [PATCH 08/31] [release-branch.go1.26] html/template: fix escaper bypass by treating empty script type as JavaScript Thank you to Mundur (https://github.com/M0nd0R) for reporting this issue. For #78981 Fixes #79025 Fixes CVE-2026-39826 Change-Id: I3f2e06496020ece655d156fb099ff556af8cc836 Reviewed-on: https://go-review.googlesource.com/c/go/+/771180 Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com (cherry picked from commit a63b23ffb2eebc9ca3a14c369b615ca623bb20f7) Reviewed-on: https://go-review.googlesource.com/c/go/+/772042 Reviewed-by: Neal Patel --- src/html/template/escape_test.go | 15 +++++++++++++++ src/html/template/js.go | 1 + 2 files changed, 16 insertions(+) diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go index 126dc22f330576..9d09f6abc553d3 100644 --- a/src/html/template/escape_test.go +++ b/src/html/template/escape_test.go @@ -231,6 +231,21 @@ func TestEscape(t *testing.T) { "", ``, }, + { + "scriptTypeSpace", + "", + "", + }, + { + "scriptTypeTab", + "", + "", + }, + { + "scriptTypeEmpty", + "", + "", + }, { "jsObjValueNotOverEscaped", "