{"record":{"id":"b5a2d93278396eeb","repo":"PHPOffice/PhpSpreadsheet","slug":"detected-loop-while-iterating-blocks","errorCode":null,"errorMessage":"Detected loop while iterating blocks","messagePattern":"Detected loop while iterating blocks","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Reader\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Shared/OLERead.php","lineNumber":164,"sourceCode":"            $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;\n\n            $this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs);\n            $pos += 4 * $bbs;\n\n            $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4);\n        }\n\n        // read the directory stream\n        $block = $this->rootStartBlock;\n        $this->entry = $this->readData($block);\n\n        $this->readPropertySets();\n    }\n\n    private function catchLoop(int $sbdBlock): void\n    {\n        if (in_array($sbdBlock, $this->possibleLoop, true)) {\n            throw new ReaderException('Detected loop while iterating blocks');\n        }\n        $this->possibleLoop[] = $sbdBlock;\n    }\n\n    /**\n     * Extract binary stream data.\n     */\n    public function getStream(?int $stream): ?string\n    {\n        if ($stream === null) {\n            return null;\n        }\n\n        $streamData = '';\n\n        if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) {\n            /** @var int */\n            $temp = $this->props[$this->rootentry]['startBlock'];","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Shared/OLERead.php#L146-L182","documentation":"Thrown by OLERead::catchLoop() when a block identifier repeats while walking the OLE block chains (small-block depot / directory streams). Compound documents link blocks in acyclic chains terminated by a special END marker; revisiting a block number proves the chain is cyclic, and parsing stops rather than looping forever.","triggerScenarios":"Reading a .xls whose FAT/SAT or directory block chain contains a cycle — corruption, truncation, or a deliberately crafted file. Encountered during Reader\\Xls load via OLERead::read()/readData() when following rootStartBlock and dependent chains.","commonSituations":"Corrupted downloads or partially-written files; hostile uploads built to hang naive parsers (the guard exists precisely for them); storage-level bit rot on archived .xls files.","solutions":["Retry from the original source and compare checksums; a cycle almost always means the copy is damaged.","Open the file in Excel/LibreOffice to confirm the workbook itself is readable; if not, have it re-exported.","Quarantine uploads that trigger this and alert — cyclic chains in user uploads are a strong corruption/tamper signal.","Keep PhpSpreadsheet current; loop-detection and malformed-container handling improve across releases."],"exampleFix":"// before\n$spreadsheet = \\PhpOffice\\PhpSpreadsheet\\IOFactory::load('upload.xls');\n// Detected loop while iterating blocks\n\n// after\ntry {\n    $spreadsheet = \\PhpOffice\\PhpSpreadsheet\\IOFactory::load('upload.xls');\n} catch (\\PhpOffice\\PhpSpreadsheet\\Reader\\Exception $e) {\n    if (str_contains($e->getMessage(), 'Detected loop')) {\n        quarantine('upload.xls'); // keep the bad file for analysis\n    }\n    throw new RuntimeException('Damaged workbook — please re-export and re-upload.', 0, $e);\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { $spreadsheet = \\PhpOffice\\PhpSpreadsheet\\IOFactory::load($file); }\ncatch (\\PhpOffice\\PhpSpreadsheet\\Reader\\Exception $e) {\n    if (str_contains($e->getMessage(), 'Detected loop')) {\n        quarantine($file);\n        return ['error' => 'Damaged workbook — please re-export and re-upload.'];\n    }\n    throw $e;\n}","preventionTips":["Regard loop detection as a corruption/tamper signal in user uploads, not a transient failure — do not blind-retry the same bytes.","Keep the library current: loop guards and malformed-FAT handling improve in patch releases.","Archive offending files; cyclic block chains are worth inspecting if they recur from one source."],"tags":["ole","file-corruption","infinite-loop-protection","phpspreadsheet"],"backgroundTag":"corrupted-binary-structure","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}