GHSA-8hg6-c449-896m Dompdf: Uncontrolled resource consumption based on declared BMP dimensions
Summarydompdf accepts a BMP image and generates a PDF-compatible PNG based only on its *declared* header dimensions and never bounds width × height before the image is converted through GD. A 58-byte BMP whose header declares e.g. `6000×6000` is accepted and later drives `imagecreatetruecolor($width, $height)` (and PHP's native BMP decoder) to allocate the full pixel canvas.A payload can fit in a single HTTP request: the BMP can be inlined as a `data:image/bmp;base64,…` URI inside attacker-controlled HTML, so no upload, no remote fetch, and no chroot-reachable file is required. It was demonstrated that a **169-byte** request drove dompdf to render to **~412 MB peak RSS and ~4.8 s** of CPU/wall time, versus ~34 MB for an identically-sized benign request — roughly a 12× memory amplification per request, repeatable and unauthenticated.DetailsRoot causeThe image is processed based on declared dimensions and type alone — no pixel budget:For BMPs that `getimagesize()` does not fully parse, dompdf trusts the raw header fields:At conversion time the canvas is allocated from those declared dimensions, before any check that enough pixel data exists:There is no maximum width/height or maximum total-pixel guard anywhere on this path.Source-to-sinkAttacker HTML reaches `Dompdf::loadHtml()` with `<img src="data:image/bmp;base64,…">` (or any BMP `src`).`Dompdf::render()` decorates frames; `Frame\Factory` marks `<img>` as an image; `FrameDecorator\Image` calls `Image\Cache::resolve_url()`.`Image\Cache::resolve_url()` accepts the BMP on declared dimensions/type (`src/Image/Cache.php:131-134`).During render, `Adapter\CPDF::image()` identifies the BMP and calls `_convert_to_png()` (`src/Adapter/CPDF.php:593`).`_convert_to_png()` invokes `Helpers::imagecreatefrombmp()`, which allocates the full canvas — via the native `imagecreatefrombmp()` on PHP ≥ 7.2, or the hand-rolled `imagecreatetruecolor()` fallback otherwise.PoCerified against dompdf @ `a6ddc4f` on PHP 8.3.6 with GD enabled.The crafted BMP is 58 bytes: a 14-byte file header + 40-byte `BITMAPINFOHEADER` declaring the target width/height at 24bpp + 4 padding bytes. Inlined as a data URI, the full attacker payload is 169 bytes:(The base64 above decodes to a 58-byte BMP declaring `6000×6000`. The CSS `width:1px;height:1px` does not help the defender — the intrinsic decode happens regardless.)1 — Direct conversionThe PHP-managed peak is under 1 MB while RSS is 160 MB: the canvas lives in GD's native allocator, so `memory_limit` does not bound it.2 — Full `Dompdf::render()`3 — HTTP reproduction (curl / Burp)Reproduced against a minimal PDF endpoint (`server.php`, included) that simply renders posted HTML — the shape of any invoice/report/HTML-to-PDF service. The endpoint sets `isRemoteEnabled=false`; the attack still works because `data:` URIs are an allowed protocol by default and need no remote fetch.curl:Burp Repeater (enable "Update Content-Length"):Observed (peak RSS read from the worker's `/proc/<pid>/status` `VmHWM`, each on a fresh worker so the high-water mark is per-request):Two identically sized 169-byte requests; the only difference is the dimensions declared inside the 58-byte BMP. The attack request costs ~378 MB extra native memory and ~5 s CPU. The cost scales with declared `width × height`, bounded only by the 32-bit header fields and the host's available memory (the process is OOM-killed before the theoretical maximum).ImpactA single unauthenticated 169-byte request forces ~400 MB of native allocation and several seconds of CPU in the rendering worker. PDF rendering is typically done by a small pool of PHP-FPM or queue workers; a handful of concurrent requests exhausts that pool's memory and stalls or OOM-kills workers, denying service to legitimate users. Because the heavy allocation is in GD's native allocator, a per-request `memory_limit` does **not** contain it.**Caveat:** this is a resource-exhaustion (DoS) primitive, not data disclosure or code execution. Some deployments already sandbox dompdf behind render timeouts, worker memory caps (cgroups), or job isolation — those reduce real-world impact. However, the specific GD implementation on a system may not be constrained by PHP limits, allowing system-level resource consumption beyond those allocated to PHP.
CVSS v4.0 score: 6.3