Skip to content

runtime: scanobject can use incorrect span metadata #11267

@aclements

Description

@aclements

When scanobject looks up the span for a pointer it read from the object it's scanning, it's possible for it to get incorrect information for the span, causing it to ignore the pointer, or, much worse, attempt to mark uninitialized memory.

  1. scanobject loads a pointer slot out of the object it's scanning, and this happens to be one of the special pointers from the heap into a stack. Call the pointer p and suppose it points into X's stack.
  2. X, running on another thread, grows its stack and frees its old stack.
  3. The old stack happens to be large or was the last stack in its span, so X frees this span, setting it to state _MSpanFree.
  4. scanobject calls heapBitsForObject, which loads the span containing p, which is now in state _MSpanFree, so it ignore it. There's currently a disabled test here that would panic (issue runtime: reenable bad pointer check in GC #10591).

or

  1. The span gets reused as a heap span.
  2. scanobject calls heapBitsForObject, which loads the span containing p, which is now in state _MSpanInUse, but doesn't necessarily have an object at p. The not-object at p gets marked, and at this point all sorts of things can go wrong.

This is quite easy to reproduce with a well-placed sleep:

diff --git a/src/runtime/mbitmap.go b/src/runtime/mbitmap.go
index 146ffbf..fe36f3a 100644
--- a/src/runtime/mbitmap.go
+++ b/src/runtime/mbitmap.go
@@ -190,6 +190,9 @@ func heapBitsForObject(p uintptr) (base uintptr, hbits heapBits, s *mspan) {
        // Consult the span table to find the block beginning.
        k := p >> _PageShift
        s = h_spans[idx]
+       if s != nil && s.state == _MSpanStack {
+               usleep(100)
+       }
        if s == nil || pageID(k) < s.start || p >= s.limit || s.state != mSpanInUse {
                if s == nil || s.state == _MSpanStack {
                        // If s is nil, the virtual address has never been part of the heap.
@@ -201,7 +204,7 @@ func heapBitsForObject(p uintptr) (base uintptr, hbits heapBits, s *mspan) {
                // The following ensures that we are rigorous about what data
                // structures hold valid pointers.
                // TODO(rsc): Check if this still happens.
-               if false {
+               if true {
                        // Still happens sometimes. We don't know why.
                        printlock()
                        print("runtime:objectstart Span weird: p=", hex(p), " k=", hex(k))

With this sleep, GOGC=10 ./runtime.test -test.short crashes reliably for me.

@RLH @rsc

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions