Skip to content

Commit 02ff75a

Browse files
cherrymuiandrew-d
authored andcommitted
[release-branch.go1.19] runtime: make GC see object as allocated after it is initialized
When the GC is scanning some memory (possibly conservatively), finding a pointer, while concurrently another goroutine is allocating an object at the same address as the found pointer, the GC may see the pointer before the object and/or the heap bits are initialized. This may cause the GC to see bad pointers and possibly crash. To prevent this, we make it that the scanner can only see the object as allocated after the object and the heap bits are initialized. Currently the allocator uses freeindex to find the next available slot, and that code is coupled with updating the free index to a new slot past it. The scanner also uses the freeindex to determine if an object is allocated. This is somewhat racy. This CL makes the scanner use a different field, which is only updated after the object initialization (and a memory barrier). Updates golang#54596. Fixes golang#56752. Change-Id: I2a57a226369926e7192c253dd0d21d3faf22297c Reviewed-on: https://go-review.googlesource.com/c/go/+/449017 Reviewed-by: Austin Clements <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Run-TryBot: Cherry Mui <[email protected]> TryBot-Result: Gopher Robot <[email protected]> (cherry picked from commit febe7b8) Reviewed-on: https://go-review.googlesource.com/c/go/+/453235
1 parent a8ce6fd commit 02ff75a

File tree

4 files changed

+22
-1
lines changed

4 files changed

+22
-1
lines changed

src/runtime/malloc.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,16 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
10681068
// the garbage collector could follow a pointer to x,
10691069
// but see uninitialized memory or stale heap bits.
10701070
publicationBarrier()
1071+
// As x and the heap bits are initialized, update
1072+
// freeIndexForScan now so x is seen by the GC
1073+
// (including convervative scan) as an allocated object.
1074+
// While this pointer can't escape into user code as a
1075+
// _live_ pointer until we return, conservative scanning
1076+
// may find a dead pointer that happens to point into this
1077+
// object. Delaying this update until now ensures that
1078+
// conservative scanning considers this pointer dead until
1079+
// this point.
1080+
span.freeIndexForScan = span.freeindex
10711081

10721082
// Allocate black during GC.
10731083
// All slots hold nil so no scanning is needed.

src/runtime/mbitmap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ func (s *mspan) nextFreeIndex() uintptr {
224224
// been no preemption points since ensuring this (which could allow a
225225
// GC transition, which would allow the state to change).
226226
func (s *mspan) isFree(index uintptr) bool {
227-
if index < s.freeindex {
227+
if index < s.freeIndexForScan {
228228
return false
229229
}
230230
bytep, mask := s.allocBits.bitp(index)

src/runtime/mgcsweep.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ func (sl *sweepLocked) sweep(preserve bool) bool {
625625

626626
s.allocCount = nalloc
627627
s.freeindex = 0 // reset allocation index to start of span.
628+
s.freeIndexForScan = 0
628629
if trace.enabled {
629630
getg().m.p.ptr().traceReclaimed += uintptr(nfreed) * s.elemsize
630631
}

src/runtime/mheap.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,14 @@ type mspan struct {
456456
limit uintptr // end of data in span
457457
speciallock mutex // guards specials list
458458
specials *special // linked list of special records sorted by offset.
459+
460+
// freeIndexForScan is like freeindex, except that freeindex is
461+
// used by the allocator whereas freeIndexForScan is used by the
462+
// GC scanner. They are two fields so that the GC sees the object
463+
// is allocated only when the object and the heap bits are
464+
// initialized (see also the assignment of freeIndexForScan in
465+
// mallocgc, and issue 54596).
466+
freeIndexForScan uintptr
459467
}
460468

461469
func (s *mspan) base() uintptr {
@@ -1235,6 +1243,7 @@ HaveSpan:
12351243

12361244
// Initialize mark and allocation structures.
12371245
s.freeindex = 0
1246+
s.freeIndexForScan = 0
12381247
s.allocCache = ^uint64(0) // all 1s indicating all free.
12391248
s.gcmarkBits = newMarkBits(s.nelems)
12401249
s.allocBits = newAllocBits(s.nelems)
@@ -1602,6 +1611,7 @@ func (span *mspan) init(base uintptr, npages uintptr) {
16021611
span.specials = nil
16031612
span.needzero = 0
16041613
span.freeindex = 0
1614+
span.freeIndexForScan = 0
16051615
span.allocBits = nil
16061616
span.gcmarkBits = nil
16071617
span.state.set(mSpanDead)

0 commit comments

Comments
 (0)