Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

fix: bound untrusted GeoJSON input to prevent parser resource exhaustion#1733

Open
agilira wants to merge 1 commit into
googlemaps:maingooglemaps/android-maps-utils:mainfrom
agilira:fix/geojson-parser-resource-exhaustionagilira/android-maps-utils:fix/geojson-parser-resource-exhaustionCopy head branch name to clipboard
Open

fix: bound untrusted GeoJSON input to prevent parser resource exhaustion#1733
agilira wants to merge 1 commit into
googlemaps:maingooglemaps/android-maps-utils:mainfrom
agilira:fix/geojson-parser-resource-exhaustionagilira/android-maps-utils:fix/geojson-parser-resource-exhaustionCopy head branch name to clipboard

Conversation

@agilira

@agilira agilira commented Jul 25, 2026

Copy link
Copy Markdown

Summary

Harden GeoJsonParser against resource exhaustion when parsing untrusted GeoJSON, by bounding the input before it is materialised. This closes the remaining gap left by the nesting-depth guards added in #1699 and #1710.

Problem

#1699 (MAX_GEOMETRY_DEPTH) and #1710 both guard against deeply nested input, but the GeoJSON guard runs during a second pass over the tree that Json.parseToJsonElement() has already fully materialised:

fun parse(inputStream: InputStream): GeoJsonObject? {
    val json = inputStream.bufferedReader().use { it.readText() }
    val jsonElement = Json.parseToJsonElement(json)   // whole document materialised here
    ...                                                // MAX_GEOMETRY_DEPTH is only checked after this

So for GeoJSON the guard acts too late. With an untrusted document a caller can still hit:

  • Deep nesting → StackOverflowError. kotlinx-serialization's JSON reader recurses on nested arrays, so a few thousand nested arrays overflow the stack inside parseToJsonElement, before MAX_GEOMETRY_DEPTH is ever reached. (KmlParser avoids this by bounding depth during streaming with DepthLimitingReader; GeoJSON has no equivalent.)
  • Wide, shallow document → OutOfMemoryError. A large flat array (e.g. a MultiPoint with millions of positions) materialises to far more heap than the source text, with no nesting for a depth guard to catch.
  • Non-finite coordinates. "Infinity"/"NaN" are accepted by String.toDouble() and currently flow straight into LatLng/LatLngBounds.

The first two throw java.lang.Error subclasses, so the catch (e: Exception) in DataLayerLoader does not contain them and the failure propagates to the host app.

Fix

Bound the input up front, before parseToJsonElement():

  • readTextBounded() — caps the number of characters read (configurable, default 10 MiB), so an oversized document is never fully materialised.
  • checkStructuralDepth() — rejects raw {/[ nesting beyond a limit, in a single string-aware pass (configurable, default 512 — safe on small Android thread stacks, and far above any legitimate GeoJSON, whose structural depth stays well under 100 even with maximally nested geometries).
  • parseCoordinates() — rejects non-finite values.

Both limits are constructor parameters with safe defaults, mirroring KmzParser's configurable zip-bomb limits (#1677). Rejected input throws IllegalArgumentException (an Exception), so DataLayerLoader degrades to a null layer instead of crashing.

Testing

Adds unit tests for each case (deep-nested arrays, excessively nested GeometryCollection, oversized input, non-finite coordinate). Existing behaviour is preserved, including the #1699 testDeeplyNestedGeometryCollectionDoesNotThrowStackOverflow case (depth 200 stays well within the structural limit and still parses).

Compatibility

GeoJsonParser's new parameters have defaults, so GeoJsonParser() continues to work unchanged for all existing (Kotlin) call sites (DataLayerLoader, GeoJsonLayer). No @JvmOverloads is added, matching the sibling KmzParser.

@google-cla

google-cla Bot commented Jul 25, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

googlemaps#1699 and googlemaps#1710 added nesting-depth guards, but they run on the tree that
Json.parseToJsonElement() has already fully materialised, so they act too late:
GeoJsonParser.parse() reads and materialises the whole untrusted document
before any check. A hostile layer can therefore still

  - overflow the parser stack with deep nesting (StackOverflowError) before the
    MAX_GEOMETRY_DEPTH=20 guard runs (contrast KmlParser, which bounds depth
    *during* streaming via DepthLimitingReader);
  - exhaust the heap with a wide, shallow document (OutOfMemoryError), which no
    depth guard addresses;
  - forward non-finite coordinates ("Infinity"/"NaN", accepted by
    String.toDouble()) straight into LatLng/LatLngBounds.

The first two throw java.lang.Error subclasses, so the catch (Exception) in
DataLayerLoader does not contain them and the host app crashes on load.

Bound the input up front, before parseToJsonElement:

  - readTextBounded() caps the characters read (configurable, default 10 MiB);
  - checkStructuralDepth() rejects raw '{'/'[' nesting beyond a limit in a
    single string-aware pass (configurable, default 512 - safe on small Android
    stacks, far above any legitimate GeoJSON);
  - parseCoordinates() rejects non-finite values.

Limits are constructor parameters with safe defaults, mirroring KmzParser.
Existing behaviour is preserved (incl. the googlemaps#1699 depth-200 GeometryCollection
test); adds tests for each case.
@agilira
agilira force-pushed the fix/geojson-parser-resource-exhaustion branch from 0e1e861 to 7a73969 Compare July 25, 2026 15:36
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

Morty Proxy This is a proxified and sanitized view of the page, visit original site.