Skip to content

fix(data): reject impossible FlatGeobuf index sizes - #965

Merged
giswqs merged 1 commit into
opengeos:mainfrom
choupeanut:fix/779-flatgeobuf-index-validation
Oct 26, 2026
Merged

giswqs merged 1 commit into
opengeos:mainfrom
choupeanut:fix/779-flatgeobuf-index-validation

Conversation

@choupeanut

@choupeanut choupeanut commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

An indexed FlatGeobuf header can advertise a feature count that overflows flatgeobuf's packed R-tree size calculation. A generated file with its count changed to 2^63 panics during select_all instead of returning a read error.

Validate each opened reader before index operations, using the existing checked level calculation and its feature-count bound, then checking that the index byte size fits usize (including wasm32). Impossible indexes now return a format error. Validation also runs when a local path is reopened, during geometry-type detection, and when the remote reader is opened over fetched ranges.

This is a focused reader fix under #779; it does not implement or close the broader fuzzing proposal. No new dependency or format change.

Testing

  • Generated regression reproduces the dependency's multiplication-overflow panic before the fix.
  • cargo test -p rustgis-data flatgeobuf: 5 passed, including the new malformed-count regression and existing local/range-backed round trips.
  • Regression covers bytes, high-level imports, local opening, a file changed after opening (full and extent requests), and range-backed opening.
  • cargo xtask ci: 2,406 passed, 0 failed, 16 ignored; layers passed.
  • uvx pre-commit run --all-files
  • cargo build --locked --target wasm32-unknown-unknown -p rustgis-web

Related issue

Part of #779.

Summary by CodeRabbit

  • Bug Fixes
    • FlatGeobuf files with impossible spatial index sizes are now rejected with a format error when opened from local files, byte data, or remote sources.

@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 8f1668af-56cf-47de-be79-1dd20657170d
Commits

Reviewing files that changed from the base of the PR and between 29d9b3e and 3223c32.

Files selected for processing: 2
  • crates/data/src/source/fgb.rs
  • crates/data/src/source/tests.rs

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Walkthrough

FlatGeobuf reader creation now validates computed spatial index sizes. Local and remote read paths, source creation, and geometry probing use the shared validation. Tests check that several oversized feature counts return format errors across these entry points.

Changes: FlatGeobuf index-size validation

Layer / File(s) Summary
Validate computed index size
crates/data/src/source/fgb.rs
A shared reader opener checks whether the advertised spatial index size can be computed and converted to usize. It returns a format error when either check fails.
Apply validation to reader entry points
crates/data/src/source/fgb.rs, crates/data/src/source/tests.rs
Source creation, local reads, geometry probing, and remote reads use the shared opener. Tests verify format errors for oversized feature counts across byte, layer-list, path, reopened-path, and remote entry points.

Priority: Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Possibly related issues

  • opengeos/RustGIS issue 779: The change rejects one malformed FlatGeobuf input before index operations. It does not add the issue’s proposed fuzzing infrastructure.

Suggested reviewers: giswqs

Merge Risk: Minimal · up to 3223c

The described reader paths reject impossible index sizes with a format error. No additional issue is established that needs resolution before merging.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title "fix(data): reject impossible FlatGeobuf index sizes" matches the main change. The diff adds an open_reader validation in fgb.rs that rejects FlatGeobuf files whose advertised spatial inde…
Description check Passed The description follows the template's three sections. Summary explains the problem (an indexed header with an overflowing feature count panics in select_all), the validation added, and the scope (not…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title "fix(data): reject impossible FlatGeobuf index sizes" matches the main change. The diff adds an open_reader validation in fgb.rs that rejects FlatGeobuf files whose advertised spatial index size cannot be represented, and adds a regression test for impossible feature counts. The title is specific, concise, and follows a conventional-commit style.

Full details: Description check

Explanation

The description follows the template's three sections. Summary explains the problem (an indexed header with an overflowing feature count panics in select_all), the validation added, and the scope (not closing the broader fuzzing proposal under #779). Testing lists the regression test, cargo test for the flatgeobuf module, cargo xtask ci, pre-commit, and a wasm32 build. Related issue states "Part of #779." The description is thorough and specific. Minor note: the Testing section lists commands and results but does not include a screenshot or manual steps, which the template does not require for this change.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks each index size,
And spots the counts that grow too wide.
The reader stops with errors clear,
While tests check paths both far and near.
Then hops away with files in gear.

Comment @coderabbitai help to get the list of available commands.

let bytes = level_bounds(h.features_count(), u64::from(h.index_node_size()))
// The dependency includes a root even when there is just one leaf.
.and_then(|levels| levels.first().and_then(|(_, end)| (*end).max(2).checked_mul(NODE)));
if bytes.and_then(|n| usize::try_from(n).ok()).is_none() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium confidence. On wasm32 this branch also rejects valid but very large indexes (roughly 100M+ features, where the index passes 4 GiB), and reports them as "an impossible spatial index size". Those files aren't impossible; they just don't fit a 32-bit address space. The message should say that, e.g. "FlatGeobuf: its spatial index is too large for this platform", with the impossible-count wording reserved for the level_bounds None case. Also, bytes.and_then(|n| usize::try_from(n).ok()).is_none() reads awkwardly. This is clearer, and is_none_or is already used in this file:

Suggested change
if bytes.and_then(|n| usize::try_from(n).ok()).is_none() {
if bytes.is_none_or(|n| usize::try_from(n).is_err()) {

let slot = header._tab.vtable().get(flatgeobuf::Header::VT_FEATURES_COUNT) as usize;
assert_ne!(slot, 0);
let offset = 8 + header._tab.loc() + slot;
for count in [1u64 << 63, u64::MAX, (1u64 << 40) + 1] {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

High confidence on the gap. All three counts (2^63, u64::MAX, 2^40 + 1) are rejected by the items > 1 << 40 cap in level_bounds. That means the new usize::try_from check, which the PR description says covers wasm32, and the single-leaf max(2) root path are never exercised by this regression test. Suggest pulling the size check into a small helper that takes the byte limit, e.g. fn index_fits(bytes: u64, max: u64) -> bool, and unit-testing it with max = u32::MAX as u64 so the 32-bit branch is covered on 64-bit CI.

@github-actions

Copy link
Copy Markdown
Contributor

Code review

Bugs

  • None found. I traced the new open_reader check through every path that opens a FlatGeobuf reader (new, read_with, peek_kind, the remote sections/remote_search setup). search is reached only after read_with has validated the header, so the index arithmetic never sees an unchecked count. Confidence: medium-high. I could not compile or run the tests here, so this rests on reading the code.

Security

  • None found.

Performance

  • None found. The check is header-only and O(log n) per open, which is negligible next to the existing header parse.

Quality

  • The wasm32 index-size branch is untested. All three regression counts are rejected by the items > 1 << 40 cap before the usize check runs, so neither the size-limit branch nor the single-leaf max(2) path is covered on 64-bit CI. Confidence: high. (Inline comment on tests.rs:236.)
  • On wasm32, a valid index between roughly 100M features and the 2^40 cap is reported as "an impossible spatial index size". The message is misleading for that case. Confidence: medium. (Inline comment on fgb.rs:493, which also includes a readability suggestion.)
  • The (*end).max(2) root allowance mirrors flatgeobuf's allocation behaviour, which I could not verify from the repository. The comment should cite the dependency function it mirrors. Confidence: low-medium.

AGENTS.md

  • Compliant. The change adds no unwrap/expect/panic, no unsafe, and no std::time. It keeps the wasm build valid and adds a regression test for the crash, as the "never crash" rule requires. No new dependency, so no licence or notices update is needed.

Change size

Compared with main (merge base d779339 to PR head 3223c32): the whole PR, not the latest push.

Measure Value
Files changed 2 (0 added, 2 modified, 0 removed, 0 renamed)
Size before 112.0 KB
Size after 114.1 KB
Total size change +2.1 KB
Path Before After Change
crates/data/src/source/tests.rs 84.7 KB 86.1 KB +1.5 KB
crates/data/src/source/fgb.rs 27.3 KB 28.1 KB +691 B

@github-actions

Copy link
Copy Markdown
Contributor

📖 Docs preview

Item Value
Preview https://preview.rustgis.app/pr-965/
Web app https://preview.rustgis.app/pr-965/app/
Commit 3223c32

GitHub Pages takes a minute or two to publish a new commit.

@giswqs
giswqs merged commit 7916c0c into opengeos:main Oct 26, 2026
18 checks passed
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