You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When the internal renderer (serve_rendered.js) requests a tile from a local source and the tile doesn't exist, createEmptyResponse() generates a 1×1 pixel image. For raster-dem sources, this 1×1 PNG is passed to maplibre-native as valid DEM tile data. When DEMData::backfillBorder() later runs between this 1×1 tile and a real 256×256 neighbor, the dimension mismatch causes an out-of-bounds memory read — which may segfault, silently corrupt data, or appear to work depending on memory layout.
v5.3.0 and earlier: Always affected. Missing DEM tiles unconditionally go through createEmptyResponse().
v5.4.0+: The sparse flag (introduced in Allow a 'sparse' option per data source #1558) mitigates this for the default case, since non-vector sources now default to sparse: true → callback(). However, explicitly setting sparse: false on a raster-dem source still routes through createEmptyResponse(), triggering the same bug. Or setting sparse: false globally as well.
Root cause
createEmptyResponse() was designed for raster image sources where a 1×1 transparent image is visually harmless. For raster-dem sources, tile dimensions are critical — backfillBorder copies pixel rows between neighbors assuming identical dimensions. Sending a 1×1 image as DEM data is semantically wrong regardless of the sparse setting.
Suggested fix
In the fetchTile == null path, always use callback() for raster-dem sources, regardless of the sparse flag:
This is analogous to HTTP 204 semantics. The tile enters a loaded-but-empty state, the empty area gets the background color, and all neighbor interactions are safely skipped.
Why not callback() for all source types?
createEmptyResponse respects sourceInfo.color — a source definition can specify what color empty tiles should be. Changing this for non-DEM sources would be a breaking change. For raster-dem sources, sourceInfo.color is meaningless (DEM tiles are elevation data, not visual), so callback() is strictly correct.
Crash evidence
Segfault from dmesg:
node[PID]: segfault at <addr> ip <addr> error 4 in mbgl.node
addr2line resolved to mbgl::DEMData::backfillBorder in tested binaries (maplibre-native 6.0.0 and 6.3.0).
Describe the bug
When the internal renderer (
serve_rendered.js) requests a tile from a local source and the tile doesn't exist,createEmptyResponse()generates a 1×1 pixel image. Forraster-demsources, this 1×1 PNG is passed to maplibre-native as valid DEM tile data. WhenDEMData::backfillBorder()later runs between this 1×1 tile and a real 256×256 neighbor, the dimension mismatch causes an out-of-bounds memory read — which may segfault, silently corrupt data, or appear to work depending on memory layout.(Reported upstream as maplibre/maplibre-native#4160)
Affected versions
createEmptyResponse().sparseflag (introduced in Allow a 'sparse' option per data source #1558) mitigates this for the default case, since non-vector sources now default tosparse: true→callback(). However, explicitly settingsparse: falseon a raster-dem source still routes throughcreateEmptyResponse(), triggering the same bug. Or setting sparse: false globally as well.Root cause
createEmptyResponse()was designed for raster image sources where a 1×1 transparent image is visually harmless. Forraster-demsources, tile dimensions are critical —backfillBordercopies pixel rows between neighbors assuming identical dimensions. Sending a 1×1 image as DEM data is semantically wrong regardless of the sparse setting.Suggested fix
In the
fetchTile == nullpath, always usecallback()forraster-demsources, regardless of the sparse flag:callback()with zero arguments takes maplibre-native's designed "no content" path:TileLoader→setData(nullptr)raster_dem_tile_worker.cpp→ skips image decodingraster_dem_tile.cpp→renderable = falserender_raster_dem_source.cpp→isRenderable()returns false → backfill loop skippedThis is analogous to HTTP 204 semantics. The tile enters a loaded-but-empty state, the empty area gets the background color, and all neighbor interactions are safely skipped.
Why not
callback()for all source types?createEmptyResponserespectssourceInfo.color— a source definition can specify what color empty tiles should be. Changing this for non-DEM sources would be a breaking change. Forraster-demsources,sourceInfo.coloris meaningless (DEM tiles are elevation data, not visual), socallback()is strictly correct.Crash evidence
Segfault from
dmesg:addr2lineresolved tombgl::DEMData::backfillBorderin tested binaries (maplibre-native 6.0.0 and 6.3.0).Additional context
Full write-up covering how this interacts with maplibre-native and maplibre-gl-js:
https://gist.github.com/JeremyBYU/f5fbd206dc1d55276ee8ab50f43ed83b
Related: maplibre/maplibre-native#4160 (the dimension guard that should also be added in maplibre-native as defense in depth).