Skip to content

HTTPFileSource causes an OOM when a Range request to PMTiles is answered with 200 instead of 206 #4374

Description

@yuiseki

Describe the bug

A byte-range request made by HTTPFileSource (Resource::dataRange) that is answered with 200 OK (the full resource) instead of 206 Partial Content is buffered in full, with no size cap.

So when the resource is large, for example a multi-GB PMTiles archive, mbgl holds the whole file in RAM and the process dies with std::bad_alloc / OOM. The map also goes blank because tiles are never decoded.

In platform/default/src/mbgl/storage/http_file_source.cpp, HTTPRequest::writeCallback appends every received chunk unconditionally, and HTTPRequest::handleResult only inspects the status code after the whole body has already been downloaded.

This happened in production on real tile infrastructure that is fronted by the Cloudflare CDN. Cloudflare intermittently answered Range requests for a ~83 GB PMTiles archive with 200 (the full file) instead of 206, so the client buffered the entire file and OOM-crashed while the map stayed blank.

To Reproduce

End to end (reproduces the OOM): point any maplibre-native map at this style, whose pmtiles:// source is served with 200 for every range request (it is the real ~83 GB file):

https://broken-range-request.yuiseki.com/styles/osm-bright.json

Quick check with curl, a 100-byte range comes back as 200 with the full 83 GB content-length, not 206:

$ curl -sD- -o /dev/null -H "Range: bytes=0-99" https://broken-range-request.yuiseki.com/planet.pmtiles
HTTP/2 200
content-length: 82984871716
accept-ranges: bytes

Requesting a small byte range through HTTPFileSource against that URL (a Resource with dataRange = {0, 126}) then buffers the whole body into Response.data with no error, and with the real file the allocation is unbounded and the process OOMs.

Expected behavior

A range request that receives a 200 (the server ignored Range) should be treated as an error, and the transfer should be aborted before the body is buffered, instead of downloading the entire resource into memory.

Screenshots

Not applicable.

Platform information (please complete the following information):

  • Operating System: Ubuntu 24.04 LTS (x86_64)
  • Platform: C++ core, Linux default platform (platform/default, libcurl HTTP source) and maplibre-native-slint
  • Version: reproduced on maplibre-native 04c4507 (vendored via maplibre-native-slint); the affected code is unchanged on main.

Additional context

Related: #3658 is another PMTiles client crash with a different root cause (a stale header after the file is replaced), and maplibre/demotiles#35 is Cloudflare corrupting PMTiles Range responses, which was fixed on the server side. Both show that CDNs mishandling Range for PMTiles happens in the wild; this issue is about the client not OOMing when that happens.

I have a verified patch that treats a range request answered with 200 as an error and aborts the transfer before the body is buffered.
Against the endpoint above it stops immediately (dataSize=0) instead of buffering the full body.

If this is just an implementation oversight, I can send a PR right away.

AI Disclosure

I used Claude Code Opus 4.8 to investigate, reproduce and try to fix this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions