Skip to content

Only use FLOAT32 x/y/z fields in PCLBase<PCLPointCloud2> and the filters - #6512

Open
mardgez wants to merge 1 commit into
PointCloudLibrary:masterfrom
mardgez:fix/cloud2-xyz-field-layout
Open

mardgez wants to merge 1 commit into
PointCloudLibrary:masterfrom
mardgez:fix/cloud2-xyz-field-layout

Conversation

@mardgez

@mardgez mardgez commented Oct 30, 2026

Copy link
Copy Markdown

PCLBase<PCLPointCloud2>::setInputCloud takes any field named x, y or z as the coordinates, whatever its datatype, count or offset, and keeps the index of a previous cloud when the new one has no such field. The PCLPointCloud2 filters then read and write these fields as single floats. A PCD file may declare its coordinates as 1-byte or 2-byte integers (TYPE U, SIZE 1), so loading such a file and passing it to VoxelGrid, PassThrough, CropBox, ProjectInliers, ... reads (and with keep_organized, writes) past the end of the points and of the data buffer. For example, VoxelGrid<PCLPointCloud2> on a cloud with UINT8 x/y/z fields reads 4 bytes at each 1-byte field, which AddressSanitizer reports as a heap-buffer-overflow on the last point.

This PR checks the layout once, where the indices are set:

  • setInputCloud resets x_idx_, y_idx_ and z_idx_ and only takes a field that holds one FLOAT32 inside a point (with a warning otherwise), so the UNAVAILABLE checks the filters already have reject other layouts. initCompute rejects a cloud whose data is smaller than width * height * point_step.
  • The filters that did not go through these checks now do:
    • CropBox checks that x, y and z are available and reads each at its own offset (it read 12 bytes from x, assuming y and z follow).
    • CropBox and RandomSample with keep_organized write the user filter value at the checked x/y/z offsets, and use its current value (it was kept in a function-static variable, so only the first value ever set was used).
    • ExtractIndices with keep_organized writes the user filter value only into fields that can hold a float (it wrote 4 bytes into every field).
    • RadiusOutlierRemoval and StatisticalOutlierRemoval with keep_organized write the fill value at the x/y/z offsets instead of the first 12 bytes of the point.
    • ProjectInliers uses the checked x/y/z offsets of the input. As a side effect it now also works in place (with filter (*cloud) the output has no fields yet while applyFilter runs, so the lookup by name always failed).
    • PassThrough and VoxelGrid check that the filter field is a FLOAT32 inside the point in both applyFilter overloads (the Indices overload of PassThrough did not check its type; a field with COUNT 0 in a PCD file has no bytes, so at the end of a point it was read past the point), and VoxelGrid checks that the filter field exists before computing the bounds (it read fields[-1]).
  • getMinMax (PCLPointCloud2) and RangeImage::extractFarRanges, which read fields as floats outside of PCLBase, check that they are FLOAT32 and inside the data.

Clouds made with toPCLPointCloud2 from the PCL point types, or read from PCD files with float coordinates, are not affected.

Tests: test_filters gets one test per filter (VoxelGrid.FieldLayout*, PassThrough.FieldLayout*, CropBox.FieldLayout*, ExtractIndices.FieldLayoutKeepOrganizedSmallField, RadiusOutlierRemoval.FieldLayoutKeepOrganizedXYZOffsets, StatisticalOutlierRemoval.FieldLayoutKeepOrganizedXYZOffsets, RandomSample.FieldLayoutKeepOrganizedXYZNotFloat32, ProjectInliers.FieldLayoutXYZNotFloat32, PCLBase.FieldLayout*), and test_common gets PCL.GetMinMaxFieldLayout and PCL.ExtractFarRangesFieldLayout. Built with AddressSanitizer, the whole test_filters, test_common and test_common_io suites pass with this change; without it, all 15 new tests fail (9 of them with an AddressSanitizer heap-buffer-overflow report, the others with wrong output).

Related: PCLPointCloud2::concatenate has the same kind of layout mismatch; I will send its fix separately. VoxelGrid with downsample_all_data (the default) also copies 8-byte fields into 4-byte slots when averaging; #6508 changes the averaging for that.

This is the change from #6498, which can no longer be opened, re-opened here on today's master.

PCLBase<PCLPointCloud2>::setInputCloud took any field named x, y or z as
the coordinates, whatever its datatype, count or offset, and kept the
index of a previous cloud when the new cloud has no such field. The
filters then read and wrote these fields as single floats, so a cloud
with 1 or 2 byte coordinates (a PCD file may declare them so) was read
and written past the end of its points and of its data, and a stale
index read past the end of the fields.

setInputCloud now resets x_idx_, y_idx_ and z_idx_ and only takes a
field that holds one FLOAT32 inside a point, so that the existing
UNAVAILABLE checks of the filters reject other layouts. initCompute
rejects a cloud whose data is smaller than width * height * point_step.

The filters that did not go through these checks now do:
- CropBox checks that x, y and z are available and reads each at its
  own offset instead of 12 bytes from x.
- CropBox and RandomSample with keep_organized write the user filter
  value at the checked x/y/z offsets, and use its current value instead
  of the first one set (it was kept in a function-static variable).
- ExtractIndices with keep_organized writes the user filter value only
  into fields that can hold a float.
- RadiusOutlierRemoval and StatisticalOutlierRemoval with keep_organized
  write the fill value at the x/y/z offsets instead of the first 12
  bytes of the point.
- ProjectInliers uses the checked x/y/z offsets of the input, which also
  makes it work in place (the output has no fields yet then).
- PassThrough and VoxelGrid check that the filter field is a FLOAT32
  inside the point (the Indices overload of PassThrough did not check
  its type, and a field with COUNT 0 has no bytes, so at the end of a
  point it was read past the point). VoxelGrid checks that the filter
  field exists before computing the bounds, instead of reading
  fields[-1].

getMinMax (PCLPointCloud2) and RangeImage::extractFarRanges, which read
fields as floats outside of PCLBase, check that they are FLOAT32 and
inside the data.

This branch has not been deployed

No deployments
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