Skip to content

[filters] Reject voxel indices beyond int range in VoxelGridCovariance and VoxelGrid - #6492

Merged
mvieth merged 5 commits into
PointCloudLibrary:masterfrom
spicysharkco:fix/voxel-grid-covariance-index-overflow
Oct 22, 2026
Merged

mvieth merged 5 commits into
PointCloudLibrary:masterfrom
spicysharkco:fix/voxel-grid-covariance-index-overflow

Conversation

@spicysharkco

@spicysharkco spicysharkco commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #6174.

VoxelGridCovariance::applyFilter already rejects input if the number of voxels would overflow an int, but it only looks at the extent (max_p - min_p) * inverse_leaf_size_. The indices of the first and last voxel, floor (min_p * inverse_leaf_size_) and floor (max_p * inverse_leaf_size_), are then cast to int without a range check. For a cloud far from the origin and a small leaf size they do not fit, which is undefined behavior, and distinct points end up in the same leaf.

Input from the issue: 10 points at x = 1e7 + i, leaf size 1e-3 along x. getLeaves ().size () is 1 instead of 10 (also what I get on arm64). VoxelGrid::applyFilter has the same pattern and returns 1 point for that input, so it is fixed in a second commit.

The fix checks, per axis and in float (the type of the expression that is cast), that both indices fit into an int. If not, it warns with the existing message and returns the same way as the existing check: empty output for VoxelGridCovariance, the unfiltered input for VoxelGrid.

I did not subtract min_p before multiplying, as suggested in the issue. That moves the voxel boundaries from multiples of the leaf size to min_p + k * leaf size, so different points would share a leaf and the NDT results of existing users would change. Rejecting also fits the public interface, which exposes the grid with int coordinates (getMinBoxCoordinates, getGridCoordinates, getCentroidIndexAt). If a correct result for such clouds is preferred, that would be a separate change.

Tests: VoxelGridCovariance.VoxelIndicesBeyondIntRange and VoxelGrid.VoxelIndicesBeyondIntRange in test/filters/test_filters.cpp, along each axis. Offsets 0 and +-1e6 keep working, offsets +-1e7 and clouds where only the smallest or only the largest index is out of range are rejected. Without the fix the rejected cases fail (1 or 2 leaves or points instead of the expected result), with the fix the whole test_filters binary passes.

A third commit clears leaves_ at the start of VoxelGridCovariance::applyFilter. Before, every early return (no input, the existing extent check, and now the index check) left the leaves of the previous input in place, so a reused filter answered getLeaves, getLeaf and getNeighborhoodAtPoint from the old grid. VoxelGridCovariance.RejectedInputClearsLeaves covers that, and VoxelGrid.VoxelIndicesAtIntRangeBounds checks the values at both ends of the int range (2^31 - 128 and -2^31 are accepted, 2^31 and -2^31 - 256 are rejected). I can move the third commit to its own PR if you prefer.

VoxelGrid<PCLPointCloud2> (filters/src/voxel_grid.cpp), UniformSampling and VoxelGridLabel have the same unchecked cast and are not changed here.

The existing check only limits the number of voxels. For a cloud far from
the origin and a small leaf size, floor (coordinate * inverse leaf size)
does not fit into an int. The cast is undefined behavior and distinct
points end up in the same leaf. Warn and return like for a too large
number of voxels.
VoxelGrid has the same check as VoxelGridCovariance, which only limits the
number of voxels. For a cloud far from the origin and a small leaf size,
floor (coordinate * inverse leaf size) does not fit into an int, the cast
is undefined behavior and distinct points end up in the same leaf. Warn and
return the input like for a too large number of voxels.
applyFilter returned before clearing the leaves when there was no input
or the leaf size was too small for the input, so a reused filter kept
the leaves of its previous input, and getLeaves, getLeaf and
getNeighborhoodAtPoint answered from the old grid.

Clear the leaves at the start of applyFilter, next to the leaf indices.
Comment on lines +634 to +635
indices_fit = indices_fit && min_index >= -2147483648.0f && min_index < 2147483648.0f &&
max_index >= -2147483648.0f && max_index < 2147483648.0f;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR! Is it possible to replace these "magic number" with some constant, probably static_cast<float>(std::numeric_limits<int>::lowest()) and static_cast<float>(std::numeric_limits<int>::max()), respectively? I think that would make the intent clearer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, @mvieth. Done in both voxel_grid.hpp and voxel_grid_covariance.hpp in 9ece07d. static_cast<float>(std::numeric_limits<int>::max()) is 2^31 as a float, so the < keeps the same exclusive upper bound and the behavior is unchanged.

Name the bounds of the int range instead of spelling out 2^31 as float literals; the values are unchanged.

@mvieth mvieth left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@mvieth
mvieth merged commit 82291b9 into PointCloudLibrary:master Oct 22, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[filters] Potentially undefined behavior in VoxelGridCovariance

2 participants