Skip to content

Validate S-102 grid dimension metadata before allocating - #46

Open
SemanticWave-Hoyeon wants to merge 1 commit into
S-100ExpertTeam:mainfrom
SemanticWave-Hoyeon:harden/s102-grid-metadata-validation
Open

SemanticWave-Hoyeon wants to merge 1 commit into
S-100ExpertTeam:mainfrom
SemanticWave-Hoyeon:harden/s102-grid-metadata-validation

Conversation

@SemanticWave-Hoyeon

Copy link
Copy Markdown

What this changes

S102_VG_BathymetryCoverage::Read() computed its buffer size as numLat * numLon * 2 in int. Both operands are read from the numPointsLatitudinal / numPointsLongitudinal attributes of the file being opened, so a product above INT_MAX wraps and the allocation ends up smaller than the dataset that H5Dread(..., H5S_ALL, ...) subsequently writes into it.

There is a second route to the same outcome that does not involve the arithmetic at all. H5S_ALL makes HDF5 read the whole dataset as described by the file's own dataspace, and that dataspace was never compared against the attribute values the allocation was sized from. Attributes that simply disagree with the stored dataspace produce the same undersized buffer.

The change

Read() now performs, in order:

  1. a range check that rejects absent, zero or negative dimensions,
  2. a multiplication that reports overflow instead of wrapping,
  3. a bound on the resulting point count,
  4. a check that the compound element width matches what the unpacking loop assumes, and
  5. a comparison of the declared dimensions against the dataspace H5Dread will actually use.

Allocation failure is reported rather than thrown, and the type, dataspace and dataset handles are released on every exit path.

One note for reviewers on step 3: the bound is 100,000,000 points, which is roughly 270x the ~600x600 grid that S-102 11.2.2 suggests, so legitimate high-resolution products are unaffected. At that bound a single coverage can still ask for about 1.6 GB across the three buffers, so please lower it if the target hardware has a tighter budget. It is a named constant for that reason.

Two follow-on problems are fixed along with it, because the validation is not much use while they remain:

  • S102_FI_BathymetryCoverage::Read() discarded the return value and returned true unconditionally, which left callers walking depth[] and uncertainty[] after a read that never populated them. It now propagates the result, and closes the group handle it opens.
  • S102H5::CreateBitmap() and S102H5::SetPositive() recomputed width * height in int and indexed depth[] with the result, repeating the original pattern in two more places. Both now iterate over the count Read() recorded, and return early when no grid was read. S102_VG_BathymetryCoverage gained a pointCountRead member to make that count available.

What was and was not verified

The validation logic was extracted and exercised standalone against a benign 600x600 grid, dimension pairs whose 32-bit product wraps, attribute/dataspace mismatches, non-positive and oversized dimensions, non-compound and unexpected-width element types, a non-2D dataspace, and a failing H5Dopen. The benign grid is accepted and the read fits its allocation; the rest are refused before anything is allocated. Under AddressSanitizer the same harness reproduces the out-of-bounds write on the current code and reports none on this branch.

I could not build the full project: it needs MSVC, MFC and the Windows HDF5 build, and I do not have that toolchain here. So this has not been compiled as part of GISLibrary, and it has not been run against real S-102 products or exercised through the UI. Both are worth doing before merge, and I am happy to adjust anything the build turns up.

S102_VG_BathymetryCoverage::Read() sized its buffer with
numLat * numLon * 2 evaluated in int. Both operands come from the
numPointsLatitudinal / numPointsLongitudinal attributes of the file being
opened, so a product above INT_MAX wraps and the allocation ends up
smaller than the dataset that H5Dread(H5S_ALL) subsequently writes into
it. Nothing coupled the size computed from the attributes to the size
HDF5 takes from the dataset's own dataspace, so a file whose attributes
disagree with its dataspace reached the same result without any wrap.

Read() now rejects non-positive dimensions, multiplies with overflow
reported instead of wrapped, bounds the resulting point count, requires
the compound element width that the unpacking loop assumes, and compares
the declared dimensions against the dataspace H5Dread will actually use.
Allocation failure is reported rather than thrown, and the type, dataspace
and dataset handles are released on every path.

Two follow-on problems are fixed with it. S102_FI_BathymetryCoverage::Read()
discarded the return value and returned true unconditionally, leaving
callers to walk depth[] and uncertainty[] after a read that never
populated them; it now propagates the result and closes the group handle.
S102H5::CreateBitmap() and S102H5::SetPositive() recomputed width * height
in int and indexed depth[] with the result; both now iterate over the
count that Read() recorded and return early when no grid was read.
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