Skip to content

Fix crashes in GeoJsonDocumentRasterOverlayTileProvider::loadTileImage#1314

Open
baruchInsert-tech wants to merge 3 commits intoCesiumGS:mainfrom
baruchInsert-tech:fix/use-after-free-geojson-loadtileimage
Open

Fix crashes in GeoJsonDocumentRasterOverlayTileProvider::loadTileImage#1314
baruchInsert-tech wants to merge 3 commits intoCesiumGS:mainfrom
baruchInsert-tech:fix/use-after-free-geojson-loadtileimage

Conversation

@baruchInsert-tech
Copy link

@baruchInsert-tech baruchInsert-tech commented Feb 28, 2026

Summary

Three bugs in GeoJsonDocumentRasterOverlayTileProvider::loadTileImage that caused native crashes when roaming around a tileset with a GeoJSON raster overlay.

Details

Bug 1: Dangling reference capture of _tree

In GeoJsonDocumentRasterOverlayTileProvider::loadTileImage(), the runInWorkerThread lambda captured _tree by reference (&tree = this->_tree). All other captures in the lambda were correctly by value.

RasterOverlayTileProvider is reference-counted via ReferenceCountedNonThreadSafe, and the lambda did not capture any reference-counted pointer to the tile provider. When tiles were unloaded (e.g., camera moves away), the tile provider could be destroyed before queued worker thread tasks executed, leaving the &tree reference dangling.

Fix:

  • Changed _tree from Quadtree to std::shared_ptr<Quadtree> so worker thread lambdas can share ownership
  • Capture pTree by value (shared_ptr copy) in the lambda to keep the quadtree alive
  • Also capture _pDocument by value since the Quadtree holds raw const GeoJsonObject* pointers into the document data

Bug 2: Dangling raw pointer in QuadtreeGeometryData::pStyle

QuadtreeGeometryData stored a const VectorStyle* raw pointer to the tile provider's _defaultStyle. When the tile provider was destroyed, worker threads still accessed the freed style through this pointer.

Fix:

  • Changed QuadtreeGeometryData::pStyle from const VectorStyle* to VectorStyle style stored by value

Bug 3: Zero-dimension texture size crash

The texture size computation could produce a zero width or height when getTargetScreenPixels() returns a small value in one dimension. Dividing by maximumScreenSpaceError and truncating to int produced 0, causing Blend2D to crash when creating a zero-size image.

Fix:

  • Clamp textureSize to a minimum of 1×1 with glm::max(..., glm::ivec2(1))

Closes #1313

@j9liu

baruchInsert-tech and others added 2 commits February 28, 2026 13:00
…Also capture _pDocument by value since the Quadtree holds raw pointers into the document data.
Change QuadtreeGeometryData::pStyle from a raw pointer to the tile
provider's _defaultStyle to a VectorStyle stored by value. The raw
pointer could dangle when the tile provider was destroyed while worker
threads were still rasterizing tiles.

Also clamp the computed textureSize to a minimum of 1x1 to prevent
Blend2D from crashing when getTargetScreenPixels() / maximumScreenSpaceError
truncates to zero in one dimension.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@baruchInsert-tech baruchInsert-tech changed the title Fix use-after-free in GeoJsonDocumentRasterOverlayTileProvider::loadTileImage Fix crashes in GeoJsonDocumentRasterOverlayTileProvider::loadTileImage Feb 28, 2026
@j9liu j9liu requested a review from azrogers March 2, 2026 18:21
@azrogers
Copy link
Contributor

azrogers commented Mar 2, 2026

@baruchInsert-tech The first and third changes you list seem like good fixes. However, I'm not sure about the second one. The pStyle pointer will be a pointer to either the style field on a GeoJsonObject in _pDocument, or it will be a pointer to the default style. It should not cause problems if it is a pointer to a style field, as _pDocument being a shared pointer ensures that this pointer will be valid for as long as the worker thread maintains a copy of the shared pointer. I can see how it might cause problems if it is a pointer to the default style, because the default style is stored on the tile provider and so could be invalid by the time the worker thread is rasterizing the tile. However, I would prefer not to copy the style for every object as this will increase memory usage (64 bytes for VectorStyle versus 4 or 8 bytes for a pointer) which could be significant for large datasets. I would suggest instead adding the _defaultStyle to the quadtree and passing that to addPrimitivesToData so that we can have a pointer to the default style that we can be sure will have the same lifetime as the worker thread lambda. This will involve re-ordering the initialization so that the quadtree object exists before addPrimitivesToData is called, but this shouldn't be too much of an issue.

Besides that, this change is looking good!

@baruchInsert-tech
Copy link
Author

@azrogers
Thanks for the review! I agree that copying the full VectorStyle for every object is wasteful. I've pushed a new commit that implements your suggestion:

  • Reverted QuadtreeGeometryData::pStyle back to a raw pointer
  • Added defaultStyle to the Quadtree struct
  • Re-ordered buildQuadtree() so the Quadtree (with its defaultStyle) is created before addPrimitivesToData is called, ensuring all pStyle pointers to tree.defaultStyle remain valid for the lifetime of the shared_ptr
  • Removed _defaultStyle from the tile provider since the quadtree now owns it

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.

Crashes in GeoJsonDocumentRasterOverlayTileProvider::loadTileImage

2 participants