VYPR
Medium severity6.0GHSA Advisory· Published May 19, 2026· Updated May 19, 2026

FPDI: Memory Exhaustion and Endless Loop in FPDI leads to Denial of Service

CVE-2026-45802

Description

Impact

This is a significant Denial of Service (DoS) vulnerability. Any application that uses FPDI to process user-supplied PDF files is at risk. An attacker can upload a small, malicious PDF file that will cause the server-side script to crash due to memory exhaustion or a script time-out. Repeated attacks can lead to sustained service unavailability.

Patches

Fixed as of version 2.6.7

Workarounds

No.

References

No.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

FPDI prior to 2.6.7 is vulnerable to DoS via a malicious PDF causing memory exhaustion or script timeout.

Vulnerability

FPDI versions before 2.6.7 are vulnerable to denial of service (DoS). An attacker can supply a specially crafted PDF file that, when processed by FPDI, causes memory exhaustion or an endless loop leading to script timeout [1][3].

Exploitation

An attacker does not need any special privileges; the vulnerability is exploitable by uploading a malicious PDF to any application that uses FPDI to process user-supplied PDF files. The attack vector is network-based and requires no authentication [1].

Impact

Successful exploitation results in denial of service: the server-side script crashes due to memory exhaustion or script timeout. Repeated attacks can lead to sustained service unavailability [1][3].

Mitigation

The vulnerability is fixed in FPDI version 2.6.7. Users should upgrade immediately. No workarounds are available [1][3].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

3
1695cfcc7e01

Merge commit from fork

https://github.com/setasign/fpdiJan SlabonMay 13, 2026Fixed in 2.6.7via llm-release-walk
8 files changed · +67 12
  • composer.lock+15 9 modified
    @@ -1615,22 +1615,28 @@
             },
             {
                 "name": "tecnickcom/tcpdf",
    -            "version": "6.11.2",
    +            "version": "6.11.3",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/tecnickcom/TCPDF.git",
    -                "reference": "e1e2ade18e574e963473f53271591edd8c0033ec"
    +                "reference": "b18f6119161019916c5bb07cb8da5205ae5c1b63"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/e1e2ade18e574e963473f53271591edd8c0033ec",
    -                "reference": "e1e2ade18e574e963473f53271591edd8c0033ec",
    +                "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/b18f6119161019916c5bb07cb8da5205ae5c1b63",
    +                "reference": "b18f6119161019916c5bb07cb8da5205ae5c1b63",
                     "shasum": ""
                 },
                 "require": {
                     "ext-curl": "*",
                     "php": ">=7.1.0"
                 },
    +            "suggest": {
    +                "ext-gd": "Enables additional image handling in some workflows.",
    +                "ext-imagick": "Enables additional image format support when available.",
    +                "ext-zlib": "Recommended for compressed streams and related features.",
    +                "tecnickcom/tc-lib-pdf": "Modern replacement for TCPDF for new projects."
    +            },
                 "type": "library",
                 "autoload": {
                     "classmap": [
    @@ -1661,8 +1667,8 @@
                         "role": "lead"
                     }
                 ],
    -            "description": "TCPDF is a PHP class for generating PDF documents and barcodes.",
    -            "homepage": "http://www.tcpdf.org/",
    +            "description": "Deprecated legacy PDF engine for PHP. For new projects use tecnickcom/tc-lib-pdf.",
    +            "homepage": "https://tcpdf.org",
                 "keywords": [
                     "PDFD32000-2008",
                     "TCPDF",
    @@ -1674,15 +1680,15 @@
                 ],
                 "support": {
                     "issues": "https://github.com/tecnickcom/TCPDF/issues",
    -                "source": "https://github.com/tecnickcom/TCPDF/tree/6.11.2"
    +                "source": "https://github.com/tecnickcom/TCPDF"
                 },
                 "funding": [
                     {
                         "url": "https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ",
    -                    "type": "custom"
    +                    "type": "paypal"
                     }
                 ],
    -            "time": "2026-03-03T08:58:10+00:00"
    +            "time": "2026-04-21T17:00:18+00:00"
             },
             {
                 "name": "theseer/tokenizer",
    
  • src/PdfParser/CrossReference/CrossReferenceException.php+5 0 modified
    @@ -76,4 +76,9 @@ class CrossReferenceException extends PdfParserException
          * @var int
          */
         const ENCRYPTED = 0x010C;
    +
    +    /**
    +     * @var int
    +     */
    +    const CYCLIC_STRUCTURE = 0x010D;
     }
    
  • src/PdfParser/CrossReference/CrossReference.php+11 1 modified
    @@ -64,6 +64,7 @@ public function __construct(PdfParser $parser, $fileHeaderOffset = 0)
     
             $offset = $this->findStartXref();
             $reader = null;
    +        $offsets = [$offset];
             /** @noinspection TypeUnsafeComparisonInspection */
             while ($offset != false) { // By doing an unsafe comparsion we ignore faulty references to byte offset 0
                 try {
    @@ -83,7 +84,16 @@ public function __construct(PdfParser $parser, $fileHeaderOffset = 0)
                 $this->readers[] = $reader;
     
                 if (isset($trailer->value['Prev'])) {
    -                $offset = $trailer->value['Prev']->value;
    +                $nextOffset = $trailer->value['Prev']->value;
    +                if (!\in_array($nextOffset, $offsets, true)) {
    +                    $offsets[] = $nextOffset;
    +                    $offset = $nextOffset;
    +                } else {
    +                    throw new CrossReferenceException(
    +                        'Cross-references includes cyclic structure.',
    +                        CrossReferenceException::CYCLIC_STRUCTURE
    +                    );
    +                }
                 } else {
                     $offset = false;
                 }
    
  • src/PdfReader/Page.php+13 2 modified
    @@ -123,7 +123,13 @@ public function getAttribute($name, $inherited = true)
                     });
     
                     if (\count($inheritedKeys) > 0) {
    -                    $parentDict = PdfType::resolve(PdfDictionary::get($dict, 'Parent'), $this->parser);
    +                    $ensuredObjectList = [];
    +                    $parentDict = PdfType::resolve(
    +                        PdfDictionary::get($dict, 'Parent'),
    +                        $this->parser,
    +                        false,
    +                        $ensuredObjectList
    +                    );
                         while ($parentDict instanceof PdfDictionary) {
                             foreach ($inheritedKeys as $index => $key) {
                                 if (isset($parentDict->value[$key])) {
    @@ -134,7 +140,12 @@ public function getAttribute($name, $inherited = true)
     
                             /** @noinspection NotOptimalIfConditionsInspection */
                             if (isset($parentDict->value['Parent']) && \count($inheritedKeys) > 0) {
    -                            $parentDict = PdfType::resolve(PdfDictionary::get($parentDict, 'Parent'), $this->parser);
    +                            $parentDict = PdfType::resolve(
    +                                PdfDictionary::get($parentDict, 'Parent'),
    +                                $this->parser,
    +                                false,
    +                                $ensuredObjectList
    +                            );
                             } else {
                                 break;
                             }
    
  • tests/_files/pdfs/specials/page_parent_loop.pdf+0 0 added
  • tests/_files/pdfs/specials/xref_prev_loop.pdf+0 0 added
  • tests/functional/PdfParser/CrossReference/CrossReferenceTest.php+9 0 modified
    @@ -612,4 +612,13 @@ public function testBehaviourWithWrongObjectTypeAttXrefOffset()
             $this->expectExceptionCode(CrossReferenceException::INVALID_DATA);
             new CrossReference($parser);
         }
    +
    +    public function testBehaviorWithSameOffsets()
    +    {
    +        $stream = StreamReader::createByFile(__DIR__ . '/../../../_files/pdfs/specials/xref_prev_loop.pdf');
    +        $parser = new PdfParser($stream);
    +        $this->expectException(CrossReferenceException::class);
    +        $this->expectExceptionCode(CrossReferenceException::CYCLIC_STRUCTURE);
    +        new CrossReference($parser);
    +    }
     }
    \ No newline at end of file
    
  • tests/functional/PdfReader/PageTest.php+14 0 modified
    @@ -4,6 +4,7 @@
     
     use PHPUnit\Framework\TestCase;
     use setasign\Fpdi\PdfParser\PdfParser;
    +use setasign\Fpdi\PdfParser\PdfParserException;
     use setasign\Fpdi\PdfParser\StreamReader;
     use setasign\Fpdi\PdfReader\DataStructure\Rectangle;
     use setasign\Fpdi\PdfReader\PdfReader;
    @@ -134,4 +135,17 @@ public function testGetExternalLinks($path, $expectedData)
                 }
             }
         }
    +
    +    public function testGetAttributeWithRecursion()
    +    {
    +        $stream = StreamReader::createByFile(__DIR__ . '/../../_files/pdfs/specials/page_parent_loop.pdf');
    +        $parser = new PdfParser($stream);
    +
    +        $pdfReader = new PdfReader($parser);
    +        $page = $pdfReader->getPage(1);
    +
    +        $this->expectException(PdfParserException::class);
    +        $this->expectExceptionMessage('Indirect reference recursion detected (4).');
    +        $page->getAttribute('Rotate');
    +    }
     }
    
7d101f321483

Fixed handling of recursion in faulty page tree.

https://github.com/setasign/fpdiJan SlabonMay 13, 2026Fixed in 2.6.7via llm-release-walk
1 file changed · +13 2
  • src/PdfReader/Page.php+13 2 modified
    @@ -123,7 +123,13 @@ public function getAttribute($name, $inherited = true)
                     });
     
                     if (\count($inheritedKeys) > 0) {
    -                    $parentDict = PdfType::resolve(PdfDictionary::get($dict, 'Parent'), $this->parser);
    +                    $ensuredObjectList = [];
    +                    $parentDict = PdfType::resolve(
    +                        PdfDictionary::get($dict, 'Parent'),
    +                        $this->parser,
    +                        false,
    +                        $ensuredObjectList
    +                    );
                         while ($parentDict instanceof PdfDictionary) {
                             foreach ($inheritedKeys as $index => $key) {
                                 if (isset($parentDict->value[$key])) {
    @@ -134,7 +140,12 @@ public function getAttribute($name, $inherited = true)
     
                             /** @noinspection NotOptimalIfConditionsInspection */
                             if (isset($parentDict->value['Parent']) && \count($inheritedKeys) > 0) {
    -                            $parentDict = PdfType::resolve(PdfDictionary::get($parentDict, 'Parent'), $this->parser);
    +                            $parentDict = PdfType::resolve(
    +                                PdfDictionary::get($parentDict, 'Parent'),
    +                                $this->parser,
    +                                false,
    +                                $ensuredObjectList
    +                            );
                             } else {
                                 break;
                             }
    
13521ef3d6a5

Fixed handling of /Prev value in view to recursion

https://github.com/setasign/fpdiJan SlabonMay 13, 2026Fixed in 2.6.7via llm-release-walk
2 files changed · +16 1
  • src/PdfParser/CrossReference/CrossReferenceException.php+5 0 modified
    @@ -76,4 +76,9 @@ class CrossReferenceException extends PdfParserException
          * @var int
          */
         const ENCRYPTED = 0x010C;
    +
    +    /**
    +     * @var int
    +     */
    +    const CYCLIC_STRUCTURE = 0x010D;
     }
    
  • src/PdfParser/CrossReference/CrossReference.php+11 1 modified
    @@ -64,6 +64,7 @@ public function __construct(PdfParser $parser, $fileHeaderOffset = 0)
     
             $offset = $this->findStartXref();
             $reader = null;
    +        $offsets = [$offset];
             /** @noinspection TypeUnsafeComparisonInspection */
             while ($offset != false) { // By doing an unsafe comparsion we ignore faulty references to byte offset 0
                 try {
    @@ -83,7 +84,16 @@ public function __construct(PdfParser $parser, $fileHeaderOffset = 0)
                 $this->readers[] = $reader;
     
                 if (isset($trailer->value['Prev'])) {
    -                $offset = $trailer->value['Prev']->value;
    +                $nextOffset = $trailer->value['Prev']->value;
    +                if (!\in_array($nextOffset, $offsets, true)) {
    +                    $offsets[] = $nextOffset;
    +                    $offset = $nextOffset;
    +                } else {
    +                    throw new CrossReferenceException(
    +                        'Cross-references includes cyclic structure.',
    +                        CrossReferenceException::CYCLIC_STRUCTURE
    +                    );
    +                }
                 } else {
                     $offset = false;
                 }
    

Vulnerability mechanics

Root cause

"Missing cycle detection in PDF cross-reference table traversal and page-tree parent resolution allows an attacker to craft a PDF that causes infinite loops, leading to memory exhaustion or script timeout."

Attack vector

An attacker uploads a small, specially crafted PDF file to any application that uses FPDI to process user-supplied PDFs. The malicious PDF contains either a cyclic cross-reference table (via repeated /Prev trailer pointers) or a cyclic page-tree parent chain (via repeated /Parent dictionary entries). When FPDI parses the file, the traversal loops indefinitely, consuming CPU and memory until the script crashes or times out. Repeated submissions can cause sustained denial of service. No authentication or special network position is required beyond the ability to upload a PDF.

Affected code

The vulnerability spans two code paths. In `src/PdfParser/CrossReference/CrossReference.php`, the constructor follows /Prev trailer pointers without tracking visited offsets, enabling cyclic cross-reference loops. In `src/PdfReader/Page.php`, the `getAttribute()` method traverses /Parent dictionary entries without cycle detection, enabling cyclic page-tree loops. Both are exercised when parsing a user-supplied PDF.

What the fix does

The patches add cycle detection in two places. In `CrossReference.php` [patch_id=898941], the parser now tracks all previously seen cross-reference offsets in an `$offsets` array; if a /Prev value points to an offset already visited, it throws a new `CrossReferenceException::CYCLIC_STRUCTURE` instead of looping forever. In `Page.php` [patch_id=898942][patch_id=898943], the `getAttribute` method passes an `$ensuredObjectList` accumulator to `PdfType::resolve`; when the same parent dictionary is resolved again, the resolver detects the recursion and throws a `PdfParserException`. Both changes convert unbounded loops into bounded, exception-based failures, preventing memory exhaustion.

Preconditions

  • inputAttacker must be able to upload or supply a PDF file to an application using FPDI.
  • configThe application must use FPDI to parse the user-supplied PDF (no special configuration required; default usage is vulnerable).

Generated on May 20, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.