Skip to content

Fix heap buffer overflow for high valence subdivision vertices - #632

Open
stefanatwork wants to merge 2 commits into
masterfrom
fix/subdiv-ring-valence-overflow
Open

stefanatwork wants to merge 2 commits into
masterfrom
fix/subdiv-ring-valence-overflow

Conversation

@stefanatwork

Copy link
Copy Markdown
Collaborator

The one-ring construction in CatmullClark1RingT::init() and GeneralCatmullClark1RingT::init() wrote into the fixed capacity ring buffers (MAX_RING_FACE_VALENCE / MAX_RING_EDGE_VALENCE) without any runtime bound, only asserts guarded these writes. A vertex shared by more than MAX_RING_FACE_VALENCE faces (or a ring with more than MAX_RING_EDGE_VALENCE edges) thus corrupted the heap in release builds.

The ring construction now stops when the ring buffers are exhausted and clamps the evaluation start indices accordingly, so no out of bounds write can happen for any topology.

In addition patches whose rings exceed the supported size limits can now get detected in constant time through a flag that is calculated at commit time for every topology. rtcInterpolate and rtcInterpolateN, which did not perform any validity check before, use this flag to reject such patches and return zeros. The ray tracing path already rejected these patches through SubdivMesh::valid.

The one-ring construction in CatmullClark1RingT::init() and
GeneralCatmullClark1RingT::init() wrote into the fixed capacity ring
buffers (MAX_RING_FACE_VALENCE / MAX_RING_EDGE_VALENCE) without any
runtime bound, only asserts guarded these writes. A vertex shared by
more than MAX_RING_FACE_VALENCE faces (or a ring with more than
MAX_RING_EDGE_VALENCE edges) thus corrupted the heap in release builds.

The ring construction now stops when the ring buffers are exhausted and
clamps the evaluation start indices accordingly, so no out of bounds
write can happen for any topology.

In addition patches whose rings exceed the supported size limits can now
get detected in constant time through a flag that is calculated at commit
time for every topology. rtcInterpolate and rtcInterpolateN, which did
not perform any validity check before, use this flag to reject such
patches and return zeros. The ray tracing path already rejected these
patches through SubdivMesh::valid.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread kernels/subdiv/half_edge.h Outdated
PatchType patch_type; //!< stores type of subdiv patch
VertexType vertex_type; //!< stores type of the start vertex
char align[2];
char valid_sizes; //!< stores if the patch and all its rings are within the supported size limits

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a bool

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and just call it valid

Comment thread kernels/subdiv/half_edge.h Outdated
/*! tests if the ring around the start vertex is within the supported size
* limits. In contrast to validRing this test only depends on the topology
* and not on the vertex positions. */
__forceinline bool validRingSizes() const

@svenwoop svenwoop Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do not need that function at all, there is some SubdivMesh::valid() function that is used in the builder to detect valid faces. Same function should also be used to filter out invalid faces for interpolate.

Comment thread kernels/subdiv/catmullclark_ring.h Outdated

do
{
/* stop when the ring buffers are exhausted to avoid writing out of bounds */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you understand all these changes in this file? As we now validate the size of the ring upfront, there should be no need for these checks at all!?

Comment thread kernels/common/scene_subdiv_mesh.cpp Outdated
foreach_unique(valid1,primID,[&](const vbool4& valid1, const unsigned int primID)
{
/* patches that exceed the supported valence limits cannot get evaluated */
if (unlikely(!topo->getHalfEdge(primID)->hasValidSizes()))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be on the save side, pull out topo->getHalfEdge(primID) into variable and use it here and in code below. want to avoid compiler to compute this twice.

Comment thread kernels/common/scene_subdiv_mesh.cpp Outdated
/* we have to calculate patch_type last! */
HalfEdge::PatchType patch_type = edge->patchType();
for (size_t i=0; i<mesh->faceVertices[f]; i++)
const char valid_sizes = edge->validPatchSizes() ? 1 : 0;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use the mesh->valid(i) function to tag a valid face/edge. This same function is also used by the builder to filter out invalid faces, we should stay consistent here.

Fold the high-valence subdivision patch check into the existing
Topology::valid() path instead of carrying a separate hasValidSizes()
query at interpolation sites. The cached half-edge flag is now a bool
and interpolation uses the same validity model as the builders, while
still checking the selected attribute topology when vertex attributes
are bound to a non-default topology.

With invalid patches filtered before PatchEval, remove the defensive
ring truncation code from the Catmull-Clark ring constructors and keep
assertions for the invariants that commit-time validation guarantees.
This avoids constructing partially truncated rings and keeps invalid
patch handling centralized in the topology validity path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
const HalfEdge* halfEdge = topo->getHalfEdge(primID);

/* invalid patches cannot get evaluated */
if (unlikely(!this->valid(primID) || !topo->valid(primID)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here should just be topo->valid(primID)? This->valid() uses the main topology which is for geometry. But even if that is invalid, if the attribute topology is still ok all is fine.

return faceValence <= MAX_RING_FACE_VALENCE && edgeValence <= MAX_RING_EDGE_VALENCE;
}

/*! tests if the ring around the start vertex is within the supported size

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not need that function, one can just use topo->valid() ?

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.

2 participants