Skip to content

SharcUpdateHit/SharcUpdateMiss: unconditional & SHARC_CACHE_INDEX_BIT_MASK turns HASH_GRID_INVALID_CACHE_INDEX into a forged in-range index → OOB atomics ~2 GiB past the accumulation buffer (device lost) #4

Description

@halukoral

Version: SHaRC v1.8.0 (SHARC_VERSION_* in SharcCommon.h), GLSL path (SHARC_ENABLE_GLSL), compiled with SHARC_ENABLE_RESPONSIVE_LIGHTING=1 and SHARC_ENABLE_SH_ENCODING=1. Observed on Windows, RTX 4090, driver 610.74, Vulkan 1.3.

Summary

When a hash-grid bucket is full, HashGridInsertEntry returns HASH_GRID_INVALID_CACHE_INDEX (0xFFFFFFFF), and SharcUpdateHit stores that sentinel into sharcState.cacheIndices[0] verbatim (there is no guard at the store site).

On the next vertex/miss, the propagation loops in both SharcUpdateHit and SharcUpdateMiss run the stored index through an unconditional mask:

tempHashGridIndex &= SHARC_CACHE_INDEX_BIT_MASK;   // SharcUpdateHit
hashGridIndex     &= SHARC_CACHE_INDEX_BIT_MASK;   // SharcUpdateMiss

This block is compiled in whenever SHARC_ENABLE_RESPONSIVE_LIGHTING is defined — the runtime isResponsiveLighting=false toggle does not bypass the mask. The mask rewrites 0xFFFFFFFF to SHARC_CACHE_INDEX_BIT_MASK (0x3FFFFFF with the default 26-bit layout), which is no longer equal to HASH_GRID_INVALID_CACHE_INDEX, so the guard inside SharcAddVoxelData passes and the interlocked adds land at:

accumulationBuffer + 0x3FFFFFF * sizeof(SharcAccumulationData)   // ≈ base + 2 GiB with the 32 B SH layout

far past the end of any realistically sized allocation → page fault → VK_ERROR_DEVICE_LOST.

Why it looks intermittent

The bug requires a full bucket, so it only fires once the table gets crowded (large sceneScale, geometry-dense views, e.g. a 100k-instance dynamic scene filling the screen). In practice this presented as "device lost after 1–2 seconds whenever the camera looks at the dense content, never otherwise".

Evidence

Diagnosed via VK_EXT_device_fault plus logging each SSBO's device-address range. With sharcCapacityLog2 = 22 (SH layout, accumulation stride 32 B):

  • accumulation SSBO: 0x1d33c00000 .. 0x1d3bc00000 (128 MiB)
  • faulting page reported by the driver: 0x1db3bff000

0x1db3bff000 is exactly the page of accumulationBase + 0x3FFFFFF * 32 — i.e. the masked sentinel index, not a random scribble. The fault addresses were bit-identical across independent device losses.

Suggested fix

Skip sentinel entries at the top of both propagation loops, before the responsive-offset block (a post-mask compare is not sufficient: with responsive lighting enabled at runtime, SharcGetResponsiveIndexOffset can shift the sentinel first):

for (...)
{
    HashGridIndex idx = sharcState.cacheIndices[i];
    if (idx == HASH_GRID_INVALID_CACHE_INDEX)
        continue;
    ...
}

With this change our reproducer (dense 25k–100k instance scene, sceneScale 100, capacity 2^22) went from crashing on every run to completing indefinitely; the fault signature has not reappeared.

An equivalent hardening alternative would be a hashGridIndex >= capacity guard inside SharcAddVoxelData, which would also catch any future index-forging path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions