Skip to content

fix(LBIE): two more out-of-bounds reads (QUAD refine signs, getGrad) - #763

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

transfix merged 1 commit into
masterfrom
fix/lbie-quad-getgrad-oob

Conversation

@transfix

Copy link
Copy Markdown
Owner

Why

AddressSanitizer found two more out-of-bounds reads in the LBIE mesher while I was fixing the compute_qef crash in #762. Neither one is that crash, and the two PRs are independent: they merge cleanly together.

QUAD: assign_refine_sign_quad indexed cells outside the grid

Octree::assign_refine_sign_quad ran find_oc_id on every edge the surface crosses. That includes edges on the volume's boundary, where xyz2octcell returns -1 for the cells outside the grid. The function then:

  • called get_err_grad(-1), which reads orig_vol about 18 KB before its start, too far away for ASan to flag;
  • read vtx_idx_arr_refine[-1], which ASan does flag;
  • could write vtx_idx_arr_refine[-1] = 1.

Any surface that leaves the volume reaches this. CI's QUAD tests never did, because they mesh 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)
  LBIE::Octree::assign_refine_sign_quad  octree.cpp:6528
  LBIE::Octree::polygonize_quad          octree.cpp:5958

OPTIMIZATION: getGrad indexed 1-based

Octree::getGrad computes t1, the derivative of the Jacobian with respect to v0 for the edges v1-v0, v2-v0 and v3-v0. Its second factors were indexed 1-based:

  • v1[3], v2[3] and v3[3] stood for z and read past the float[3] arrays. ASan reports this as a stack-buffer-overflow in LbieMesherTest.HexaDoMeshOptimize.
  • The z line used [2] where it meant y. That read stays in bounds but computes the wrong quantity.

v3[3] reads toward mmv4, the point that optimization()'s line search fills next, and mmv4 is still uninitialized on the first call. That is a likely cause of the couple of NaN vertices arm64 Release left in that test.

What

  • assign_refine_sign_quad: both of its loops skip an edge that has a cell outside the grid. is_min_edge already does the same for polygonize_quad, which never makes a quad for such an edge. An edge has either all four of its cells on the grid or two or three off it, so an off-grid cell could never have completed the 4-of-4 or 3-of-4 / opposite-pair conditions anyway. Only the undefined reads change.
  • getGrad: the three second-factor indices are lowered by one. All three t1 lines now equal dJ/dv0: checked against central differences on 1000 random tetrahedra, the maximum error is 8e-10. The old z line was off by up to 4.8.

Tests

  • New LbieMesherTest.QuadMeshesASurfaceThatLeavesTheVolume meshes a sphere of radius 1.2 in its [-1, 1]³ box as QUAD, at the default error and at a fine one.
  • HexaDoMeshOptimize checks finite coordinates again (expect_geoframe_valid instead of the topology-only check). This PR's macOS arm64 Release lane is the real test of that.
  • AddressSanitizer (Linux, GCC 13, RelWithDebInfo):

Follow-up

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.
@transfix
transfix enabled auto-merge (squash) October 22, 2026 05:17
@transfix
transfix merged commit 2ef8b73 into master Oct 22, 2026
24 checks passed
@transfix
transfix deleted the fix/lbie-quad-getgrad-oob branch October 22, 2026 05:18
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