proc/native: fix target program crash caused by call injection (linux) (#1538)

RestoreRegisters on linux would also restore FS_BASE and GS_BASE, if
the target goroutine migrated to a different thread during the call
injection this would result in two threads of the target process
pointing to the same TLS area which would greatly confuse the target
runtime, leading to fatal panics with nonsensical stack traces.

Other backends are unaffected:

- native/windows doesn't store the TLS in the same CONTEXT struct as
  the other register values.
- native/darwin doesn't support function calls (and wouldn't store the
  TLS value in the same struct)
- gdbserial/rr doesn't support function calls (because it's a
  recording)
- gsdbserial/lldb extracts the value of TLS by executing code in the
  target process.
This commit is contained in:
Alessandro Arzilli 2019-04-25 18:45:37 +02:00 committed by Derek Parker
parent cba328f834
commit 0b3c7d80cd
2 changed files with 62 additions and 1 deletions

@ -88,7 +88,19 @@ func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
var restoreRegistersErr error var restoreRegistersErr error
t.dbp.execPtraceFunc(func() { t.dbp.execPtraceFunc(func() {
restoreRegistersErr = sys.PtraceSetRegs(t.ID, (*sys.PtraceRegs)(sr.Regs)) oldRegs := (*sys.PtraceRegs)(sr.Regs)
var currentRegs sys.PtraceRegs
restoreRegistersErr = sys.PtraceGetRegs(t.ID, &currentRegs)
if restoreRegistersErr != nil {
return
}
// restoreRegisters is only supposed to restore CPU registers, not FS_BASE and GS_BASE
oldRegs.Fs_base = currentRegs.Fs_base
oldRegs.Gs_base = currentRegs.Gs_base
restoreRegistersErr = sys.PtraceSetRegs(t.ID, oldRegs)
if restoreRegistersErr != nil { if restoreRegistersErr != nil {
return return
} }

@ -4286,3 +4286,52 @@ func TestAncestors(t *testing.T) {
} }
}) })
} }
func testCallConcurrentCheckReturns(p proc.Process, t *testing.T, gid1 int) bool {
for _, thread := range p.ThreadList() {
g, _ := proc.GetG(thread)
if g == nil || g.ID != gid1 {
continue
}
retvals := thread.Common().ReturnValues(normalLoadConfig)
if len(retvals) != 0 {
return true
}
}
return false
}
func TestCallConcurrent(t *testing.T) {
protest.MustSupportFunctionCalls(t, testBackend)
withTestProcess("teststepconcurrent", t, func(p proc.Process, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture, 24)
assertNoError(proc.Continue(p), t, "Continue()")
//_, err := p.ClearBreakpoint(bp.Addr)
//assertNoError(err, t, "ClearBreakpoint() returned an error")
gid1 := p.SelectedGoroutine().ID
t.Logf("starting injection in %d / %d", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID())
assertNoError(proc.CallFunction(p, "Foo(10, 1)", &normalLoadConfig, false), t, "EvalExpressionWithCalls()")
returned := testCallConcurrentCheckReturns(p, t, gid1)
curthread := p.CurrentThread()
if curbp := curthread.Breakpoint(); curbp.Breakpoint == nil || curbp.ID != bp.ID || returned {
return
}
_, err := p.ClearBreakpoint(bp.Addr)
assertNoError(err, t, "ClearBreakpoint() returned an error")
for {
returned = testCallConcurrentCheckReturns(p, t, gid1)
if returned {
break
}
t.Logf("Continuing... %v", returned)
assertNoError(proc.Continue(p), t, "Continue()")
}
proc.Continue(p)
})
}