From 6df871afc0e01f1144abaa4acedaf8e537e386bc Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Fri, 10 Jul 2026 16:46:52 -0400 Subject: [PATCH] Fix DAC AMD64 unwinder null deref on zero read address (#126081) The DAC's AMD64 stack unwinder reads target memory via MemoryRead64/128, which resolve to dac_cast<>::operator* -> DacInstantiateTypeByAddress. That helper's `preserve special pointer values'' fast-path returns a NULL host pointer for target addresses 0 and (TADDR)-1 *without* throwing, even though the read is invoked with throwEx == true. operator* then unconditionally dereferences the NULL, faulting inside the DAC itself (observed as 'segfault at 0 ... in libmscordaccore.so' on the DBI-Callback thread when an unwind step reads at ContextRecord->Rsp == 0). Honor the read's throw-on-failure contract in the DAC build by rejecting the 0 / (TADDR)-1 special addresses in MemoryRead64/128 so the stackwalk aborts cleanly with a DacError instead of crashing. This covers every unchecked read site in the amd64 unwinder at once and is scoped to DACCESS_COMPILE, leaving the online/JIT unwinder unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/unwinder/amd64/unwinder.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index 7fadcefd758aa7..bb73fbf5a704e1 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -27,6 +27,18 @@ typedef DPTR(M128A) PTR_M128A; // static ULONG64 MemoryRead64(PULONG64 addr) { +#ifdef DACCESS_COMPILE + // DacInstantiateTypeByAddress (see dacfn.cpp) preserves the "special" target + // addresses 0 and (TADDR)-1 by returning a NULL host pointer *without* throwing, + // even when the read is supposed to throw on failure. dac_cast<>::operator* would + // then dereference that NULL and fault inside the DAC itself. Such addresses can + // never be a valid location to read from while unwinding, so honor the read's + // throw-on-failure contract here and let the stackwalk abort cleanly. + if ((TADDR)addr == 0 || (TADDR)addr == (TADDR)-1) + { + DacError(HRESULT_FROM_WIN32(ERROR_READ_FAULT)); + } +#endif // DACCESS_COMPILE return *dac_cast((TADDR)addr); } @@ -49,6 +61,15 @@ static ULONG64 MemoryRead64(PULONG64 addr) // static M128A MemoryRead128(PM128A addr) { +#ifdef DACCESS_COMPILE + // See the note in MemoryRead64: guard against the DAC's non-throwing special-address + // fast-path so a 0 / (TADDR)-1 read address aborts the stackwalk instead of faulting + // inside the DAC. + if ((TADDR)addr == 0 || (TADDR)addr == (TADDR)-1) + { + DacError(HRESULT_FROM_WIN32(ERROR_READ_FAULT)); + } +#endif // DACCESS_COMPILE return *dac_cast((TADDR)addr); }