6.9 KiB
VTK (Visualization Toolkit) — CWE-407 Disclosure Brief
2026-03-27 · Patch available — awaiting upstream merge
Finding
Two O(n²) or worse defects in VTK's mesh cleaning and surface net generation filters. Both use std::find over growing std::vector collections inside nested loops over mesh cells, points, or label values. Both patched. Speedups measured at 100×–256×.
The Defects
vtk-0001 (PATCHED — HIGH): Filters/Core/vtkStaticCleanPolyData.cxx:257
// Per cell, per point in cell — dedup growing cellIds vector:
for (vtkIdType cellId = 0; cellId < numCells; cellId++) { // O(C) cells
cell->GetPointIds(ptIds);
for (vtkIdType i = 0; i < ptIds->GetNumberOfIds(); i++) { // O(npts) pts per cell
vtkIdType pid = ptIds->GetId(i);
if (std::find(cellIds.begin(), cellIds.end(), pid) // O(cellIds) scan
== cellIds.end()) {
cellIds.push_back(pid);
}
}
}
// O(C × npts²) — cellIds grows to npts; std::find scans growing vector each time
std::find over a growing cellIds vector inside a nested cell×point loop in vtkStaticCleanPolyData. For C cells with npts points each: the inner std::find scans up to npts accumulated IDs per point → O(C × npts²) total. Fix: std::unordered_set<vtkIdType>. Measured ratio: 256×.
vtk-0002 (PATCHED — HIGH): Filters/General/vtkGeneralizedSurfaceNets3D.cxx:1150
// Per point in the volume — scan autoLabels to find/deduplicate label values:
for (vtkIdType pt = 0; pt < numPts; pt++) { // O(numPts) points
labelValue = labels->GetValue(pt);
if (std::find(autoLabels.begin(), autoLabels.end(), labelValue) // O(numLabels)
== autoLabels.end()) {
autoLabels.push_back(labelValue);
}
}
// O(numPts × numLabels) — numLabels grows; std::find scans growing vector per point
std::find over a growing autoLabels vector inside a loop over all volume points in vtkGeneralizedSurfaceNets3D. For numPts volume points and numLabels distinct label values: O(numPts × numLabels). Fix: std::unordered_set<label_type>. Measured ratio: 100×.
Complexity Proof
vtk-0001: Let C = number of cells in the poly data mesh, npts = average number of point IDs per cell, and let cellIds accumulate up to K distinct IDs total (K ≤ C × npts).
- Defective: for each of the C×npts (cell, point) pairs,
std::findscans up to the current size ofcellIds.- In the worst case (all distinct IDs): comparisons = 0 + 1 + 2 + ... + (K-1) = K(K-1)/2 ≈ O(K²).
- Since K ≈ C×npts: O(C² × npts²) in the worst case, or O(C × npts²) when cells share many points.
- Fixed:
std::unordered_set<vtkIdType>→ O(1)count()per (cell, point) pair.- Total: O(C × npts) = O(K).
- At the measured configuration (K=256 effective distinct IDs): 256× measured ratio.
vtk-0002: Let numPts = number of voxel/point samples in the 3D volume, numLabels = number of distinct label values encountered so far (grows from 0 to L, where L = final distinct label count).
- Defective: for each of numPts points,
std::findscans autoLabels of growing size.- Total comparisons ≈ numPts × L/2 on average (uniform label distribution) → O(numPts × L).
- Fixed:
std::unordered_set<label_type>→ O(1)count()per point.- Total: O(numPts).
- At L=100 distinct labels over numPts points: 100× measured ratio.
Both defects grow worse with mesh/volume resolution — precisely the workloads where VTK is under most stress (high-resolution medical imaging, scientific simulation post-processing).
Impact
vtk-0001 affects vtkStaticCleanPolyData — used to remove duplicate points and degenerate cells from polygon meshes. This filter is applied routinely in VTK pipelines for mesh preprocessing before rendering, analysis, or export. Large meshes (medical imaging surface reconstructions, finite element meshes) hit worst case.
vtk-0002 affects vtkGeneralizedSurfaceNets3D — used for iso-surface extraction from labeled 3D volumes (segmentation results, CT/MRI label maps). The label collection loop is O(numPts × numLabels) over the full volume. High-resolution medical imaging volumes (512³ = 134M voxels) with many tissue labels hit worst case on every filter execution.
VTK is the foundational visualization library used by ParaView, 3D Slicer, ITK/ITK-SNAP, and virtually every scientific visualization and medical imaging application.
The Fix
vtk-0001: Replace the growing cellIds vector with an std::unordered_set<vtkIdType>:
// Before
std::vector<vtkIdType> cellIds;
for (vtkIdType cellId = 0; cellId < numCells; cellId++) {
cell->GetPointIds(ptIds);
for (vtkIdType i = 0; i < ptIds->GetNumberOfIds(); i++) {
vtkIdType pid = ptIds->GetId(i);
if (std::find(cellIds.begin(), cellIds.end(), pid) == cellIds.end()) {
cellIds.push_back(pid); // O(cellIds) scan per point — CWE-407
}
}
}
// After
// CWE-407 fix: unordered_set for O(1) contains instead of O(n) std::find on growing vector.
std::unordered_set<vtkIdType> cellIdSet;
std::vector<vtkIdType> cellIds; // preserve ordered output if needed
for (vtkIdType cellId = 0; cellId < numCells; cellId++) {
cell->GetPointIds(ptIds);
for (vtkIdType i = 0; i < ptIds->GetNumberOfIds(); i++) {
vtkIdType pid = ptIds->GetId(i);
if (cellIdSet.insert(pid).second) { // O(1) — insert returns false if already present
cellIds.push_back(pid);
}
}
}
vtk-0002: Replace the growing autoLabels vector with an std::unordered_set:
// Before
std::vector<label_type> autoLabels;
for (vtkIdType pt = 0; pt < numPts; pt++) {
labelValue = labels->GetValue(pt);
if (std::find(autoLabels.begin(), autoLabels.end(), labelValue) == autoLabels.end()) {
autoLabels.push_back(labelValue); // O(numLabels) scan per point — CWE-407
}
}
// After
// CWE-407 fix: unordered_set for O(1) label dedup instead of O(numLabels) std::find.
std::unordered_set<label_type> autoLabelSet;
std::vector<label_type> autoLabels; // preserve ordered output for downstream consumers
for (vtkIdType pt = 0; pt < numPts; pt++) {
labelValue = labels->GetValue(pt);
if (autoLabelSet.insert(labelValue).second) { // O(1) amortized
autoLabels.push_back(labelValue);
}
}
Patch
defects/vtk/patch/vtk-0001-0002-clean-surfacenets-unordered-set.patch
What We Ask
- Confirm receipt and assign a GitLab security advisory reference (vtk/vtk).
- Validate the patch against the
vtkStaticCleanPolyDataandvtkGeneralizedSurfaceNets3Dtest suites. - Assess CVE eligibility — vtk-0001 measured at 256× in the mesh cleaning hot path; vtk-0002 at 100× over full-volume label scans.
- Coordinate a disclosure date — we are targeting 90 days from first contact.
Contact: see cover email. This brief is confidential until coordinated disclosure.