Skip to content

[DAC-Linux] Dac private interface crashes with some benign inputs #124640

@leculver

Description

@leculver

The .NET DAC on Linux crashes diagnostic tools (SOS, dotnet-dump, ClrMD) when ISOSDacInterface methods receive certain benign or invalid inputs. On Windows these are caught by SEH and returned as error HRESULTs. On Linux there is no SEH, so they become SIGSEGV and kill the process.

We systematically tested every method on ISOSDacInterface through ISOSDacInterface16 with adversarial inputs and found 13 bugs: 12 SIGSEGV crashes and 1 hang.

Crashes found

ISOSDacInterface (base) -- 5 crashes in request.cpp:

  1. GetAppDomainName(addr=0) -- null AppDomain pointer dereference
  2. TraverseVirtCallStubHeap(addr=garbage, callback=null) -- dereferences garbage domain address
  3. TraverseVirtCallStubHeap(addr=valid, callback=null) -- calls through null callback pointer
  4. TraverseModuleMap(module=valid, callback=null) -- calls through null callback pointer
  5. GetRegisterName(idx=0, bufLen=2) -- writes full register name past end of caller's buffer (buffer overflow)

ISOSDacInterface7 -- 4 crashes in request.cpp:

  1. GetPendingReJITID(md=0xFFFFFFFFFFFFFFFF) -- dereferences MaxValue as MethodDesc
  2. GetReJITInformation(md=0xFFFFFFFFFFFFFFFF) -- same
  3. GetProfilerModifiedILInformation(md=0xFFFFFFFFFFFFFFFF) -- same
  4. GetMethodsWithProfilerModifiedIL(module=0xFFFFFFFFFFFFFFFF) -- dereferences MaxValue as Module

ISOSDacInterface8 -- 3 crashes in request.cpp:

  1. GetFinalizationFillPointersSvr(heap=0xDEADBEEFCAFEBABE) -- resolves garbage as gc_heap pointer
  2. GetFinalizationFillPointersSvr(heap=0xFFFFFFFFFFFFFFFF) -- same with MaxValue
  3. GetAssemblyLoadContext(mt=0xFFFFFFFFFFFFFFFF) -- dereferences MaxValue as MethodTable

ISOSDacInterface3 -- 1 hang:

  1. GetGCGlobalMechanisms -- writes 48 bytes (6 x size_t) into caller buffer, but IDL signature is just size_t* with no count. If caller provides a smaller buffer, the write corrupts the stack causing undefined behavior (infinite loop on Linux).

Root cause categories

  • Missing address validation (8 crashes): DAC dereferences CLRDATA_ADDRESS without validating it points to readable target-process memory.
  • Missing null callback check (3 crashes): Traversal APIs call through function pointer without null-checking.
  • Buffer overflow (1 crash): GetRegisterName uses wcscpy_s but ignores the buffer length parameter.
  • Stack corruption (1 hang): GetGCGlobalMechanisms has no way to communicate required buffer size to callers.

Bonus: shadowed HRESULT bugs

Found 5 instances where HRESULT hr = S_OK; declared inside SOSDacEnter's EX_TRY block shadows the macro's outer hr. The return hr after SOSDacLeave always returns S_OK, silently swallowing errors. Affected: GetGenerationTable, GetFinalizationFillPointers, GetGenerationTableSvr, GetFinalizationFillPointersSvr, GetObjectComWrappersData.

Fixes

All 12 crashes and 5 shadowed HRESULT bugs are fixed in src/coreclr/debug/daccess/request.cpp. Forward-proofing for GetGCGlobalMechanisms buffer size is in src/coreclr/inc/dacprivate.h (RequestGlobal over-allocates to 256 entries). Caller-side hardening with input validation guards added in ClrMD (microsoft/clrmd).

Repro

Test suite at https://github.com/leculver/dac-tests -- 648 tests, standalone (no dependencies), runs against any Linux coredump + matching DAC.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions