Skip to content

fix(LBIE): keep getVertGrad on the grid (intermittent crash in compute_qef) - #762

Merged
transfix merged 1 commit into
masterfrom
fix/lbie-collapse-oob
Oct 22, 2026
Merged

transfix merged 1 commit into
masterfrom
fix/lbie-collapse-oob

Conversation

@transfix

Copy link
Copy Markdown
Owner

Why

LbieMesherTest.HexaVerticesStayInsideAVolumeTheSurfaceLeaves, #677's own regression test, hit an access violation (0xC0000005) in the recipe / windows-2022 / Release lane of #758 (run 39637628700, job 119352377930). It crashed in the r = 1.5 sphere, after "finished collapse" and before "finished compute qef". That is earlier than the hexa vertex walk #677 fixed. It is intermittent: the same lane passed on #758's previous head and in ~40 other recent runs.

Cause. Octree::interpRect3Dpts_x/y/z interpolate an edge crossing's normal between the gradients at two grid points: the one the crossing falls in and the next one. When the edge ends on the volume's far face exactly at the isovalue (ival == 1), the second point is one plane off the grid (index 17 of 17), and getVertGrad → getValue read past orig_vol. For a z edge that is up to ~1.1 KB past the end, far enough to reach an unmapped page now and then on Windows.

Why only the second sphere: sampled at 17³ in [-1, 1]³, the r = 1.5 sphere passes exactly through 24 grid points on the box's faces (8² + 4² + 8² = 12² in eighths), where r - d is exactly 0. The r = 1.2 sphere passes through none.

AddressSanitizer (Linux, GCC 13, RelWithDebInfo) flags it on every run:

heap-buffer-overflow READ of size 4, 12 bytes after the 19652-byte orig_vol (Octree::initMem)
  LBIE::Octree::getValue           octree.cpp:3598
  LBIE::Octree::getVertGrad        octree.cpp:3661
  LBIE::Octree::interpRect3Dpts_z  octree.cpp:3501
  LBIE::Octree::cell_comp          octree.cpp:606
  LBIE::Octree::compute_qef        octree.cpp:760
  LBIE::Mesher::do_duallib_extract LBIE_Mesher.cpp:84

What

getVertGrad() clamps its grid point onto the volume. In the case above the off-grid sample's interpolation weight is exactly 0, so meshes are unchanged:

  • HEXA, r = 1.2: 344 vertices / 4374 quads;
  • HEXA, r = 1.5: 64 vertices / 162 quads.

These are the counts #677 reported. Only the out-of-bounds read goes away.

Tests

  • New LbieMesherTest.MeshesASurfaceThroughGridPointsOnTheFarFaces meshes the r = 1.5 sphere as TETRA, HEXA, DOUBLE and TETRA2. It checks the geoframe and that every normal is finite.
  • Without the fix, under ASan:
    • HexaVerticesStayInsideAVolumeTheSurfaceLeaves is flagged in 40 of 40 runs (OMP_NUM_THREADS = 1, 2, 8 and the default, 10 runs each);
    • on the r = 1.5 sphere every mesh type is flagged: SINGLE, TETRA, QUAD and HEXA in compute_qef, DOUBLE and TETRA2 in compute_qef_interval.
  • With the fix, under ASan:
    • both tests pass in 40 of 40 runs, with no reports;
    • lbie_mesher_test 37/37, clean apart from the pre-existing HexaDoMeshOptimize read listed below;
    • algorithm_test 10/10 and geometry_test 111/111, clean.
  • Not run: Windows /fsanitize=address. This PR's Windows lanes are the first Windows run.

Found, not fixed (pre-existing; not this crash)

  • QUAD: Octree::assign_refine_sign_quad (from polygonize_quad) reads index -1 of an octcell-indexed array wherever the surface leaves the volume. Both the r = 1.2 and r = 1.5 spheres reach it. It is the same family as LBIE: keep the hexa vertex walk on the grid (intermittent hexahedralize crash) #677. CI's QUAD tests use r = 0.5, so they never reach it, and that is why the new test leaves QUAD out.
  • Octree::getGrad reads v1[3], v2[3] and v3[3] of float[3] arrays. ASan reports it as a stack-buffer-overflow in HexaDoMeshOptimize via Octree::optimization. A stack overread can't fault, but it feeds garbage into OPTIMIZATION's gradient, which may explain the arm64 Release NaNs that test's comment describes.
  • SINGLE extracts no surface from the r = 1.5 sphere. This is not a memory error.

…e_qef)

LbieMesherTest.HexaVerticesStayInsideAVolumeTheSurfaceLeaves hit an access
violation on windows-2022 Release (#758, run 39637628700) in its
r = 1.5 sphere, after "finished collapse" and before "finished compute qef".

Octree::interpRect3Dpts_x/y/z take a second gradient sample at the grid point
after the one an edge's crossing falls in. When the edge ends on the volume's
far face exactly at the isovalue (ival == 1), that point is one plane off the
grid, and getVertGrad()/getValue() read past orig_vol: up to about 1 KB for a
z edge. The r = 1.5 sphere, sampled at 17^3 in [-1,1]^3, passes exactly through
24 grid points on the box's faces (8^2 + 4^2 + 8^2 = 12^2 in eighths); r = 1.2
passes through none. AddressSanitizer flags the read on every run:

  heap-buffer-overflow READ of size 4, 12 bytes after the 19652-byte orig_vol
    Octree::getValue           octree.cpp:3598
    Octree::getVertGrad        octree.cpp:3661
    Octree::interpRect3Dpts_z  octree.cpp:3501
    Octree::cell_comp          octree.cpp:606
    Octree::compute_qef        octree.cpp:760

getVertGrad() now clamps its point onto the grid. The off-grid sample's weight
is 0, so meshes are unchanged (344 vertices / 4374 quads and 64 / 162 for the
test's spheres, as before).

New LbieMesherTest.MeshesASurfaceThroughGridPointsOnTheFarFaces meshes that
sphere as TETRA, HEXA, DOUBLE and TETRA2; without the fix ASan flags each of
them (compute_qef or compute_qef_interval).
@transfix
transfix enabled auto-merge (squash) October 22, 2026 05:16
@transfix
transfix merged commit f9dd980 into master Oct 22, 2026
24 checks passed
@transfix
transfix deleted the fix/lbie-collapse-oob branch October 22, 2026 05:16
transfix added a commit that referenced this pull request Oct 22, 2026
…763)

Found under AddressSanitizer while fixing the compute_qef crash (#762); neither
is that crash.

QUAD: Octree::assign_refine_sign_quad visited every edge the surface crosses,
including edges on the volume's boundary, where find_oc_id gives -1 for the
cells outside the grid. It then called get_err_grad(-1), which reads orig_vol
about 18 KB before its start, and read and wrote vtx_idx_arr_refine[-1].
polygonize_quad never makes a quad for such an edge (is_min_edge treats an
off-grid neighbour as refined and rejects it), so both loops now skip it. An
edge has either all four cells on the grid or two or three off it, so an
off-grid cell could never complete the 4-of-4 or 3-of-4 / opposite-pair
conditions anyway: only the undefined reads change. Any surface that leaves
the volume reached it; CI's QUAD tests use a sphere inside the box.

  heap-buffer-overflow READ of size 4, 4 bytes before the 18724-byte
  vtx_idx_arr_refine (4681 octree cells)
    Octree::assign_refine_sign_quad  octree.cpp:6528
    Octree::polygonize_quad          octree.cpp:5958

getGrad (OPTIMIZATION): t1, d(jacobian)/d(v0) for the edges v1-v0, v2-v0,
v3-v0, indexed its second factors 1-based: v1[3], v2[3], v3[3] (z) read past
the float[3] arrays, and the z line used [2] for y. With the indices lowered,
all three lines equal dJ/dv0 (checked against central differences on 1000
random tetrahedra, max error 8e-10; the old z line was off by up to 4.8).
ASan: stack-buffer-overflow in LbieMesherTest.HexaDoMeshOptimize, reading past
mmv3 toward mmv4, the point optimization()'s line search fills next (still
uninitialized on the first call). That is a likely cause of the NaN vertices
arm64 Release left in that test.

Tests:
- New LbieMesherTest.QuadMeshesASurfaceThatLeavesTheVolume (r = 1.2 sphere,
  default and fine error).
- HexaDoMeshOptimize checks finite coordinates again (expect_geoframe_valid);
  the arm64 Release lane is the real check of that.
- Without the fixes ASan flags both tests in 40/40 runs (OMP_NUM_THREADS 1, 2,
  8, default); with them 40/40 clean. lbie_mesher_test 37/37 ASan-clean
  apart from HexaVerticesStayInsideAVolumeTheSurfaceLeaves, whose read #762
  fixes; algorithm_test 10/10 and geometry_test 111/111 clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant