Skip to content

Commit 93e3696

Browse files
committed
cmd/compile: avoid past-the-end pointer when zeroing
When we optimize append(s, make([]T, n)...), we have to be careful not to pass &s[0] + len(s)*sizeof(T) as the argument to memclr, as that pointer might be past-the-end. This can only happen if n is zero, so just special-case n==0 in the generated code. Fixes #67255 Change-Id: Ic680711bb8c38440eba5e759363ef65f5945658b Reviewed-on: https://go-review.googlesource.com/c/go/+/584116 Reviewed-by: Austin Clements <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Cuong Manh Le <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent ad27916 commit 93e3696

File tree

2 files changed

+59
-17
lines changed

2 files changed

+59
-17
lines changed

src/cmd/compile/internal/walk/assign.go

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -623,21 +623,23 @@ func isAppendOfMake(n ir.Node) bool {
623623
// panicmakeslicelen()
624624
// }
625625
// s := l1
626-
// n := len(s) + l2
627-
// // Compare n and s as uint so growslice can panic on overflow of len(s) + l2.
628-
// // cap is a positive int and n can become negative when len(s) + l2
629-
// // overflows int. Interpreting n when negative as uint makes it larger
630-
// // than cap(s). growslice will check the int n arg and panic if n is
631-
// // negative. This prevents the overflow from being undetected.
632-
// if uint(n) <= uint(cap(s)) {
633-
// s = s[:n]
634-
// } else {
635-
// s = growslice(T, s.ptr, n, s.cap, l2, T)
626+
// if l2 != 0 {
627+
// n := len(s) + l2
628+
// // Compare n and s as uint so growslice can panic on overflow of len(s) + l2.
629+
// // cap is a positive int and n can become negative when len(s) + l2
630+
// // overflows int. Interpreting n when negative as uint makes it larger
631+
// // than cap(s). growslice will check the int n arg and panic if n is
632+
// // negative. This prevents the overflow from being undetected.
633+
// if uint(n) <= uint(cap(s)) {
634+
// s = s[:n]
635+
// } else {
636+
// s = growslice(T, s.ptr, n, s.cap, l2, T)
637+
// }
638+
// // clear the new portion of the underlying array.
639+
// hp := &s[len(s)-l2]
640+
// hn := l2 * sizeof(T)
641+
// memclr(hp, hn)
636642
// }
637-
// // clear the new portion of the underlying array.
638-
// hp := &s[len(s)-l2]
639-
// hn := l2 * sizeof(T)
640-
// memclr(hp, hn)
641643
// }
642644
// s
643645
//
@@ -671,11 +673,18 @@ func extendSlice(n *ir.CallExpr, init *ir.Nodes) ir.Node {
671673
s := typecheck.TempAt(base.Pos, ir.CurFunc, l1.Type())
672674
nodes = append(nodes, ir.NewAssignStmt(base.Pos, s, l1))
673675

676+
// if l2 != 0 {
677+
// Avoid work if we're not appending anything. But more importantly,
678+
// avoid allowing hp to be a past-the-end pointer when clearing. See issue 67255.
679+
nifnz := ir.NewIfStmt(base.Pos, ir.NewBinaryExpr(base.Pos, ir.ONE, l2, ir.NewInt(base.Pos, 0)), nil, nil)
680+
nifnz.Likely = true
681+
nodes = append(nodes, nifnz)
682+
674683
elemtype := s.Type().Elem()
675684

676685
// n := s.len + l2
677686
nn := typecheck.TempAt(base.Pos, ir.CurFunc, types.Types[types.TINT])
678-
nodes = append(nodes, ir.NewAssignStmt(base.Pos, nn, ir.NewBinaryExpr(base.Pos, ir.OADD, ir.NewUnaryExpr(base.Pos, ir.OLEN, s), l2)))
687+
nifnz.Body = append(nifnz.Body, ir.NewAssignStmt(base.Pos, nn, ir.NewBinaryExpr(base.Pos, ir.OADD, ir.NewUnaryExpr(base.Pos, ir.OLEN, s), l2)))
679688

680689
// if uint(n) <= uint(s.cap)
681690
nuint := typecheck.Conv(nn, types.Types[types.TUINT])
@@ -697,7 +706,7 @@ func extendSlice(n *ir.CallExpr, init *ir.Nodes) ir.Node {
697706
l2)),
698707
}
699708

700-
nodes = append(nodes, nif)
709+
nifnz.Body = append(nifnz.Body, nif)
701710

702711
// hp := &s[s.len - l2]
703712
// TODO: &s[s.len] - hn?
@@ -723,7 +732,7 @@ func extendSlice(n *ir.CallExpr, init *ir.Nodes) ir.Node {
723732
// if growslice isn't called do we need to do the zeroing ourselves.
724733
nif.Body = append(nif.Body, clr...)
725734
} else {
726-
nodes = append(nodes, clr...)
735+
nifnz.Body = append(nifnz.Body, clr...)
727736
}
728737

729738
typecheck.Stmts(nodes)

test/fixedbugs/issue67255.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// run
2+
3+
// Copyright 2024 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package main
8+
9+
var zero int
10+
11+
var sink any
12+
13+
func main() {
14+
var objs [][]*byte
15+
for i := 10; i < 200; i++ {
16+
// The objects we're allocating here are pointer-ful. Some will
17+
// max out their size class, which are the ones we want.
18+
// We also allocate from small to large, so that the object which
19+
// maxes out its size class is the last one allocated in that class.
20+
// This allocation pattern leaves the next object in the class
21+
// unallocated, which we need to reproduce the bug.
22+
objs = append(objs, make([]*byte, i))
23+
}
24+
sink = objs // force heap allocation
25+
26+
// Bug will happen as soon as the write barrier turns on.
27+
for range 10000 {
28+
sink = make([]*byte, 1024)
29+
for _, s := range objs {
30+
s = append(s, make([]*byte, zero)...)
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)