Skip to content

fix(tile): cap zoom in raster terrarium/mapbox handlers - #527

Open
rot1024 wants to merge 1 commit into
mainfrom
fix/tile-terrain-zoom-cap
Open

fix(tile): cap zoom in raster terrarium/mapbox handlers#527
rot1024 wants to merge 1 commit into
mainfrom
fix/tile-terrain-zoom-cap

Conversation

@rot1024

@rot1024 rot1024 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

raster_tile (backing /terrarium/{z}/{x}/{y}.{png|webp|avif} and /mapbox/...) never checked z against terrain.max_zoom before running the parent-tile upsample path in extract_and_upsample.

For z >= dem_max + log2(tile_size) + 1 (>= 27 with the shipping defaults), factor > tile_size, so extract_and_upsample computes row = off_y * tile_size up to ~511*256 and indexes parent (len tile_size**2 = 65536) far out of bounds. The always-on slice bounds check panics (bounds checks are kept in release too), the panic aborts the request task, the connection resets, and error logs flood — a cheap, unauthenticated DoS on a public endpoint. Legitimate MapLibre clients cap requests at maxzoom (terrain.max_zoom, default 18) so no real client hits this.

Fix

  • raster_tile handler: reject z > terrain.max_zoom with 404 before any coordinate math. Returns the same shape as the existing "Out of geoid coverage" case.
  • extract_and_upsample (defense-in-depth): saturating clamp on zoom_diff so factor <= tile_size, and on sub_x/sub_y so the sub-region always stays within parent. Keeps the panic path closed even if a future caller forgets the upstream guard.

Test plan

  • cargo fmt --check
  • New upsample_zoom_diff_beyond_tile_size_does_not_panic — feeds zoom_diff = 10 with factor = 1024 >> tile_size = 4 and sub_x/sub_y = 1023 (the exact shape of the OOB from a z = 27 request). Previously panicked; now returns a valid tile_size * tile_size tile.
  • New upsample_clamps_sub_x_y_at_boundary — sanity check at the largest safe zoom_diff.
  • Existing upsample_extracts_correct_quadrant and upsample_factor_one_is_passthrough still pass (clamps are no-ops in the legitimate range).
  • cargo check — sandbox has a preexisting object_store version mismatch in pmtiles.rs files unrelated to this change; CI resolves against Cargo.lock and is the source of truth.

Copilot AI review requested due to automatic review settings July 22, 2026 08:02

Copilot AI left a comment

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.

Pull request overview

This PR mitigates a panic-based denial-of-service vector in the tile service’s raster terrain endpoints by capping invalid zoom requests early and adding defense-in-depth bounds clamping in the upsample path.

Changes:

  • Reject /terrarium/... and /mapbox/... requests with z > terrain.max_zoom with a 404 before proceeding to tile math.
  • Harden extract_and_upsample by clamping zoom_diff and sub_x/sub_y to prevent out-of-bounds indexing on the parent tile.
  • Add regression tests intended to ensure out-of-range upsample inputs do not panic.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
tile/src/terrain/mod.rs Adds clamping in extract_and_upsample and new regression tests for previously panicking inputs.
tile/src/server/terrain.rs Adds an early zoom cap in raster_tile to reject out-of-range zoom requests with 404.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tile/src/terrain/mod.rs Outdated
Comment thread tile/src/terrain/mod.rs
@rot1024

rot1024 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

CI note (ci-tile failure is not caused by this PR)

The 3 red ci-tile jobs all fail with the same preexisting dependency-graph error:

error[E0277]: the trait bound `object_store::path::Path: From<object_store::path::Path>` is not satisfied
   --> src/terrain/pmtiles.rs:86
error[E0308]: mismatched types
   --> src/terrain/pmtiles.rs:86  (and src/tile/pmtiles.rs:59)

Two different versions of the object_store crate (0.14.1 and 0.13.2) are resolved into the workspace via pmtiles v0.23.1's ObjectStoreBackend::new, so Path from one version can't Into<Path> for the other. This has nothing to do with src/server/terrain.rs or src/terrain/mod.rs (the two files changed here — neither references object_store or pmtiles).

main (5ca6e31) shows green because its ci run only exercises the paths for the changed files there (ci-tile was skipped) — the tile compile error only surfaces once a PR touches tile/. So the fix belongs in a separate Cargo.toml / Cargo.lock PR that pins object_store (or bumps pmtiles), not in this security fix.

Happy to send that dep-pin PR too if you want.

@rot1024
rot1024 force-pushed the fix/tile-terrain-zoom-cap branch from bb6bc4a to af8b3df Compare July 22, 2026 11:01
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