Fix/validate mesh face indices - #3565
aWarmWalrus wants to merge 3 commits into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
|
@google-cla check CLA should work now. |
The binary (MJB) model loader reads mesh_face, mesh_polyvert and mesh_polymap verbatim from the file and only runs mj_validateReferences, which previously checked the *adr/*num arrays but not the contents of these index arrays. At runtime these values are used as unchecked offsets (e.g. mesh_vert + 3*mesh_face[...] in engine_ray.c / render_context.c / engine_collision_*.c), so a crafted .mjb with an out-of-bounds vertex index loads successfully and then causes an out-of-bounds read when the mesh is ray-cast, rendered or collided. The XML compiler already rejects such indices (mjCMesh::CheckMesh in user_mesh.cc), but that path is bypassed when loading a binary model. Add equivalent bounds checks to mj_validateReferences so both load paths are covered.
Covers the mesh_face out-of-bounds case in mj_validateReferences: a mesh face vertex index that is >= mesh_vertnum or negative must be rejected, matching the checks added for the binary (MJB) load path.
7e562ba to
3cbe397
Compare
sylvesterkaczmarek
left a comment
There was a problem hiding this comment.
This leaves the same MJB out-of-bounds-read class open through mesh_facenormal. Binary models load that array verbatim too, and both classic and Filament rendering use each entry as an unchecked local index into mesh_normal (for example mesh_normal + 3 * (mesh_facenormal[...] + normaladr)). A crafted MJB can therefore pass these new checks with a valid mesh_face but an oversized face-normal index and still read outside the normal buffer. Could mj_validateReferences also validate each mesh_facenormal entry against the owning mesh's mesh_normalnum, with a corruption regression alongside mesh_face? mesh_facetexcoord appears worth auditing for the same reason, accounting for its no-texcoord sentinel semantics.
Follow-up to the mesh_face/mesh_polyvert/mesh_polymap checks: the same MJB out-of-bounds-read class was still open through mesh_facenormal and mesh_facetexcoord. Both are read verbatim from a binary model and used as unchecked mesh-local indices by the classic and Filament renderers (mesh_normal + 3*(mesh_facenormal[...] + normaladr), and likewise for mesh_texcoord), so a crafted .mjb with valid mesh_face but an oversized face-normal or face-texcoord index could still read outside those buffers. Check every mesh_facenormal entry against the owning mesh's mesh_normalnum. Check mesh_facetexcoord entries against mesh_texcoordnum only for meshes that have texcoords (mesh_texcoordadr != -1), since the array is never read for meshes without them and the compiler leaves it unset in that case. Extend the regression test to cover both arrays, including the no-texcoord sentinel case.
0fa386c to
2cf65cd
Compare
|
Ah yes good point, updated with |
sylvesterkaczmarek
left a comment
There was a problem hiding this comment.
Re-reviewed current 2cf65cd. The added validation now covers mesh_facenormal and mesh_facetexcoord with the correct no-texcoord guard, and the regression exercises oversized and negative indices plus the mesh_texcoordadr == -1 case. My earlier blocker is resolved.
Validate mesh index arrays in
mj_validateReferencesProblem
When loading a binary model (
.mjb),mj_loadModelBufferreads the wholemjModelbuffer verbatim from the file and validates it only throughmj_validateReferences. That function checks the*adr/*numarrays but not thecontents of the mesh index arrays
mesh_face,mesh_polyvertandmesh_polymap.At runtime these are used as unchecked offsets, e.g.
mesh_vert + 3*(mesh_face[...] + mesh_vertadr[meshid])in
engine_ray.c,render_context.c,engine_collision_sdf.c,engine_support.candengine_collision_convex.c.The XML compiler already rejects out-of-range indices
(
mjCMesh::CheckMesh,user_mesh.cc), but that path is bypassed for binary models.As a result a crafted
.mjbwith an out-of-bounds vertex index loads successfullyand then causes an out-of-bounds read (crash, or silent adjacent-heap read for small
overruns) as soon as the mesh is ray-cast, rendered or collided.
Repro (before this change)
A
.mjbidentical to a valid model exceptmesh_faceset to100000000(with
nmeshvert == 4) loads viaMjModel.from_binary_pathwith no error, and asingle
mj_rayagainst the mesh segfaults.Fix
Add content bounds-checks for
mesh_face,mesh_polyvertandmesh_polymaptomj_validateReferences, mirroring the compiler's checks. The*adr/*numarraysiterated here are already validated by
MJMODEL_REFERENCESearlier in the samefunction, so the iteration is safe.
Follow-ups (not in this PR)
mesh_graph,bvh_child/bvh_nodeid, and theskin_*/flex_*index arrays sharethe same root cause and warrant equivalent validation.
Testing
Adds
ValidateReferencesTest.Meshintest/engine/engine_io_test.cc, which corruptsmesh_faceto an out-of-range and to a negative index and expectsmj_validateReferencesto reportmesh_face(and to return null once restored),following the existing
GeomCondim/Texturecases.src/engine/engine_io.cwas syntax/type-checked locally withgcc -fsyntax-only -Iinclude -Isrc(clean). The full test suite was not run in myenvironment (no MSVC/Clang toolchain set up here); please rely on CI to execute the
added test.