UE 5.8.1 — Use-after-free of FD3D12UniformBuffer in ValidateBoundUniformBuffer during World Partition traversal; garbage collection is the releasing agent

Summary

Reproducible EXCEPTION_ACCESS_VIOLATION in the D3D12 RHI while traversing a large World Partition map. A cached shader binding replays a raw, un-refcounted FRHIUniformBuffer* after the object has been destroyed, and ValidateBoundUniformBuffer faults dereferencing it.

Two things distinguish this from the earlier reports linked at the bottom, which describe the same fault site without identifying what releases the object:

Use-after-free is proven, not inferred. Under Memory.UsePurgatory the dereferenced pointer comes back as 0xdcdcdcdcdcdcdcdc — the purgatory fill pattern — so the block was freed, and freed within the four-frame quarantine window.
The releasing agent is garbage collection, confirmed four independent ways including a monotonic dose-response curve against gc.TimeBetweenPurgingPendingKillObjects.
I also believe I have found why nothing prevents this: the D3D12 backend defines ID3D12UniformBufferUpdateListener specifically to notify holders of cached bindings, and FD3D12UniformBuffer::~FD3D12UniformBuffer drains that listener list — but AddListener has zero call sites anywhere in the D3D12RHI module. The list is always empty. See section 6.

Please select what you are reporting on:

Creative

What Type of Bug are you experiencing?

Other

Steps to Reproduce

  1. Open-world World Partition map, Nanite + Lumen + VT, bindless enabled from ray tracing.
  2. Traverse continuously so cells stream in and out. Any locomotion reproduces it — on foot, in a vehicle, or flying. It is not tied to a location, an actor, or an asset.
  3. Crash follows in 100–500 s depending on the GC interval; median 213 s at the 60 s default.

Expected Result

Traversal does not crash. A uniform buffer referenced by a cached shader binding should not be able to be destroyed while that binding can still be replayed.

Observed Result

Fault site (19 of 19 traversal crashes):
UnrealEditor-D3D12RHI.dll + 0x4c6e0 mov rax, [rdi+0x48] 15 crashes
UnrealEditor-D3D12RHI.dll + 0x4c6ef mov r14d, [rax+0x90] 4 crashes

EXCEPTION_ACCESS_VIOLATION, ExceptionInformation[1] = 0xffffffffffffffff
Crashing thread: task-graph worker, 19/19 (“Foreground Worker #n” / “Background Worker #n”)

GPU breadcrumb, crashing passes:
VirtualTextureFinalizeRequests 10
Nanite::BasePass 8
Nanite::LumenMeshCapturePass 1

That is ValidateBoundUniformBuffer, D3D12RHI/Private/D3D12Commands.cpp:43-56:
inline void ValidateBoundUniformBuffer(FD3D12UniformBuffer* InUniformBuffer, FRHIShader* InShaderRHI, uint32 InBufferIndex)
{
#if DO_CHECK
auto const& LayoutHashes = InShaderRHI->GetShaderResourceTable().ResourceTableLayoutHashes;

if (InBufferIndex < (uint32)LayoutHashes.Num())          // :48 — this bounds check PASSES
{
    uint32 UniformBufferHash = InUniformBuffer->GetLayout().GetHash();   // :50 — FAULTS

The two RVAs are the two loads of that one expression. Offsets confirmed by struct layout computed from source, not assumed:

FRHIUniformBuffer::Layout = FRHIResource 0x20 + RHIValidation::FUniformBufferResource 0x18 + ResourceTable TArray 0x10 = 0x48 (RHIResources.h:1300, :1294, :221-229; RHIValidationCommon.h:594-604)
FRHIUniformBufferLayout::Hash = 0x20 + FString Name 0x10 + six TArrays 0x60 = 0x90 (RHIResources.h:1235, :1215-1233)

Platform(s)

Windows

Additional Notes

Severity — a Shipping build is not safe, it is quiet
ValidateBoundUniformBuffer is inside #if DO_CHECK (D3D12Commands.cpp:45). In Shipping it compiles away and this crash disappears while the freed uniform buffer is still bound to the GPU. One DXGI_ERROR_DEVICE_HUNG in this corpus (breadcrumb FXSystemPreRender > HairCardsInterpolation) is the likely downstream consequence. A packaged build that “doesn’t crash” is disguised, not fixed.

Suggested fix, in preference order
Register the cached binding as an ID3D12UniformBufferUpdateListener — the mechanism already exists and is documented for exactly this case; it is simply never wired up.
Give FMaterialRenderProxy::InvalidateUniformExpressionCache a material-keyed path to invalidate cached mesh draw commands, so ReleaseRHI is not relying on callers having used FMaterialUpdateContext.
Compare MaterialUBSerialNumber on the Nanite path outside the debug validator, and ungate it from bBindless.
Prior reports that appear to be the same defect
5.7.4, sampler state, same pass, 10+ machines, AMD and NVIDIA — UE 5.7.4 — Use-after-free of FD3D12SamplerState in FD3D12DescriptorCache::BuildSamplerTable during Nanite BasePass
5.5.4 packaged — D3D12 UniformBuffer Access Violation Crash in UE 5.5.4 Packaged Builds
5.5.4 intermittent — Intermittent D3D12 UniformBuffer Access Violation Crash in UE 5.5.4 Packaged Builds
FastGeo — D3D12 UniformBuffer Read After Free When FastGeo Is Enabled
ValidateUniformBuffer crash — ValidateUniformBuffer Crash
5.3 — UE 5.3 Crashes in different scenarios with the same error.
Reproduction harness
An automated in-game probe: waits for a pawn, teleports to a start point, terrain-follows while ping-ponging across the affected band, sweeps the view, records a JSONL trace, and exits on survival. Roughly 100% reproduction, median 213 s. Minidumps were parsed without a debugger by walking the MDMP stream directory for the exception record and module list and mapping the faulting address to module+RVA; the instruction bytes were disassembled straight out of the shipped DLL via a PE section walk. Happy to share the tooling or raw dumps if that would help.

Hey man, I had a similar issue with that same execution thread. I’m leaving my solution in this comment. hopefully it helps you. I spent four days dealing with that error, and it was incredibly frustrating.

1 Like

Absolutely brilliant mate, it was in front of me the whole time! Thank you.

P.S. If anyone else has this issue, my weather system is what brought it to light.