Fix out-of-bounds UDF read causing non-deterministic mesh corruption - #39
Open
rwfsmith wants to merge 1 commit into
Open
Fix out-of-bounds UDF read causing non-deterministic mesh corruption#39rwfsmith wants to merge 1 commit into
rwfsmith wants to merge 1 commit into
Conversation
get_vertex_val() looked up a voxel corner's UDF value via the hashmap but never checked whether the lookup actually found the key. When a corner lies just outside the voxelized narrow band, linear_probing_lookup returns the sentinel UINT32_MAX, and udf[UINT32_MAX] reads far out of bounds GPU memory. The resulting garbage value could push the isosurface crossing test either way, non-deterministically injecting bogus dual contouring vertices/intersections into the remeshed output. This explained the shredded/spiky mesh corruption seen when using o_voxel's to_glb(remesh=True) on Windows/Blackwell: identical code and latents sometimes produced a clean mesh and sometimes produced garbage, because the corruption depended on whatever was in that out-of-bounds GPU memory at the time. Fix: treat a missing hashmap entry as "far outside the surface" (a large positive UDF value) so no bogus intersection is produced for that edge, instead of reading uninitialized/out-of-bounds memory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Aug 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
get_vertex_val()insrc/remesh/simple_dual_contour.cuuses the result of a hashmap lookup as an array index without checking whether the lookup succeeded. On a miss this reads far out of bounds ofudf.This is undefined behavior on all platforms — it isn't Windows- or Blackwell-specific. It just happens to be visible when the out-of-bounds memory contains something that flips a comparison.
The bug
linear_probing_lookupsignals "not found" with a sentinel (src/hash/hash.cuh):With
V = uint32_tthat'sUINT32_MAX, so a miss makes the line above evaluateudf[4294967295]— a read roughly 16 GB past the base pointer.A miss is reachable in normal use:
get_vertex_valis called for all 8 corners of each voxel, and corners just outside the voxelized narrow band were never inserted into the hashmap.Worth noting this is the only call site that dereferences the result directly. The two call sites in
src/hash/hash.cujust store the returned value and let the caller handle the sentinel, which suggests this one is an oversight rather than an intentional invariant.Impact
Whatever garbage lands in
idx's slot participates in the isosurface crossing test, so it can inject spurious dual-contouring vertices and edge intersections. Downstream in TRELLIS.2 this showed up asto_glb(..., remesh=True)non-deterministically producing shredded/spiky meshes — the same code and the same latents would give a clean mesh on one run and a corrupted one on the next, because the result depended on whatever happened to be in that memory at the time.That non-determinism is what makes it worth fixing rather than working around: it's silent, intermittent, and very easy to misattribute to the model.
Fix
Treat a missing corner as "far outside the surface" so the edge simply produces no intersection, instead of reading out-of-bounds memory:
9 added lines (mostly the explanatory comment), one file, no API or behavior change for corners that are present.
Verification
Reproduced and confirmed fixed on Windows 11 / CUDA 13.0 / RTX 5090 via TRELLIS.2's
to_glb(remesh=True): corrupted exports across repeated runs before, consistently clean meshes after. Extracted GLBs check out (e.g. 808k verts / 973k faces, 0 NaNs, 6 degenerate faces, sane extents).I have not built on Linux, but the change is platform-independent and only adds a bounds guard on a path that was previously UB.