{"id":"CVE-2026-108359","aliases":["GHSA-j2w7-387v-m2rq"],"title":"PhpSpreadsheet ODS Reader: Unbounded number-rows-repeated → CPU & Memory Exhaustion (DoS)","summary":"PhpSpreadsheet ODS Reader: Unbounded number-rows-repeated → CPU & Memory Exhaustion (DoS)","severity":"high","cvss":8.7,"cwe":["CWE-400","CWE-789"],"vendor":"phpoffice","product":"phpoffice/phpspreadsheet","ecosystem":"composer","affected":["phpoffice/phpspreadsheet >= 4.0.0, <= 5.9.0","phpoffice/phpspreadsheet >= 3.3.0, <= 3.10.7","phpoffice/phpspreadsheet >= 2.2.0, <= 2.4.7","phpoffice/phpspreadsheet >= 2.0.0, <= 2.1.18","phpoffice/phpspreadsheet <= 1.30.6"],"patched":["phpoffice/phpspreadsheet 5.10.0","phpoffice/phpspreadsheet 3.10.8","phpoffice/phpspreadsheet 2.4.8","phpoffice/phpspreadsheet 2.1.19","phpoffice/phpspreadsheet 1.30.7"],"published":"2026-10-27","updated":"2026-10-27","sourceUpdated":"2026-10-27T15:38:35Z","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-j2w7-387v-m2rq","references":[{"url":"https://github.com/PHPOffice/PhpSpreadsheet/security/advisories/GHSA-j2w7-387v-m2rq"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-108359"},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/299993894dce37aa3b64aa23e9a3f4bb77ab0e66"},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/2de29384aa11223ee08136c0e6a9e9981bc1a2eb"},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/980b5c294493856f1a25754f1e8a1d2185b93738"},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/9a0b48622ebfe93fa48cb9bc09ce57303bb07af9"},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/da13f40e8760d599b0e8a296017d4b028b20e86c"},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/1.30.7"},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/2.1.19"},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/2.4.8"},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/3.10.8"},{"url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/5.10.0"},{"url":"https://github.com/advisories/GHSA-j2w7-387v-m2rq"}],"tags":["ghsa","composer","cve.org"],"epss":0.00407,"epssPercentile":0.34763,"cvssVector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","cvssSource":"cna","ingestedAt":"2026-10-22T22:00:50.355Z","slug":"CVE-2026-108359","body":"## Overview\n\n## Summary\n\nWhen PhpSpreadsheet loads an OpenDocument Spreadsheet (`.ods`), `Ods::processTableRow()`\nreads the `table:number-rows-repeated` attribute of a `<table:table-row>` with an unchecked \n`(int)` cast, then materialises one cell per repeated row. This value is **completely unbounded**, \nso a **single ~1.4 KB file** can instruct the reader to create up to **millions of cells**, \nexhausting memory (fatal OOM). No authentication, no special configuration, and the default read \nfilter are required - any application that loads a user-supplied `.ods` is affected.\n\n---\n\n## Affected versions\n\nThe `number-rows-repeated` expansion has existed in the ODS reader since its inception\n(pre-dating the current release branches), so all maintained lines are affected.\n\n| Branch | Affected |\n|--------|----------|\n| 1.x    | ≤ 1.30.6 |\n| 2.0–2.1 | ≤ 2.1.18 |\n| 2.2–2.4 | ≤ 2.4.7 |\n| 3.x    | ≤ 3.10.7 |\n| 4.x–5.x | ≤ 5.9.0 (latest) |\n\n---\n\n## Root cause (source → sink)\n\n`src/PhpSpreadsheet/Reader/Ods.php`:\n\n```php\n// line 816 — row repeat, unchecked (int) cast\n$rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-rows-repeated');\n...\n// line 1151 — row materialisation loop: NO upper bound\nfor ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) {\n    $rID = $rowID + $rowAdjust;\n    $cell = $spreadsheet->getActiveSheet()->getCell($columnID . $rID);  // allocates a Cell\n    $cell->setValueExplicit($dataValue, $type);\n    // ... no bounds check on $rowAdjust or $rID ...\n}\n```\n\n**Why columns are NOT vulnerable (already bounded):**\n\nThe column-header loop at line 1326 shows the correct pattern with bounds checking:\n\n```php\n// line 1326 (processTableColumnHeader): BOUNDED correctly\nfor ($rowRepeats2 = $rowRepeats; $rowRepeats2 > 0 && $tableColumnIndex2 <= AddressRange::MAX_COLUMN_INT; --$rowRepeats2) {\n    // HAS BOUNDS CHECK: && $tableColumnIndex2 <= AddressRange::MAX_COLUMN_INT\n}\n```\n\nAdditionally, the cell-data column loop at line 1141 calls `getCell()` which throws exception \nat `Coordinate.php:448` when column exceeds `MAX_COLUMN_INT` (XFD / 16,384):\n\n```php\n// Coordinate.php line 448\nif ($columnIndex > self::MAX_COLUMN_INT) {\n    throw new Exception('Column string index can not be beyond XFD');\n}\n```\n\n**Result:** Columns are capped at 16,384. Only rows are unbounded.\n\n---\n\n## Impact\n\n**Memory exhaustion (single valued cell, repeated many rows):**  \nA non-null cell value with a huge `number-rows-repeated` attribute materialises millions of \n`Cell` objects → PHP fatal `Allowed memory size ... exhausted`.\n\n**Example:**\n- Payload: `<table:table-row table:number-rows-repeated=\"5000000\">` (5 million rows)\n- File size: 1.4 KB\n- Memory consumed: ~256 MB (one Cell object per row)\n- Result: OOM at default memory limit\n\n---\n\n## Reproduction\n\n**Vulnerable payload (causes OOM):**\n\n```xml\n<table:table table:name=\"Sheet1\">\n <table:table-row table:number-rows-repeated=\"5000000\">\n  <table:table-cell office:value-type=\"float\" office:value=\"1\">\n   <text:p>1</text:p>\n  </table:table-cell>\n </table:table-row>\n</table:table>\n```\n\n**Not vulnerable (for reference - throws exception):**\n\n```xml\n<table:table table:name=\"Sheet1\">\n <table:table-row>\n  <table:table-cell office:value-type=\"float\" office:value=\"1\"\n      table:number-columns-repeated=\"1000000000\">\n   <text:p>1</text:p>\n  </table:table-cell>\n </table:table-row>\n</table:table>\n```\n\nThis column-repeat example throws `Column string index can not be beyond XFD` exception\n(bounded defense, already in place).\n\n## Affected packages\n\n- `phpoffice/phpspreadsheet >= 4.0.0, <= 5.9.0`\n- `phpoffice/phpspreadsheet >= 3.3.0, <= 3.10.7`\n- `phpoffice/phpspreadsheet >= 2.2.0, <= 2.4.7`\n- `phpoffice/phpspreadsheet >= 2.0.0, <= 2.1.18`\n- `phpoffice/phpspreadsheet <= 1.30.6`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `phpoffice/phpspreadsheet 5.10.0`\n- `phpoffice/phpspreadsheet 3.10.8`\n- `phpoffice/phpspreadsheet 2.4.8`\n- `phpoffice/phpspreadsheet 2.1.19`\n- `phpoffice/phpspreadsheet 1.30.7`","depth":"twilight","depthScore":48,"depthScoreParts":{"impact":47.8,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[]}