{"id":"GHSA-j2w7-387v-m2rq","summary":"PhpSpreadsheet ODS Reader: Unbounded number-rows-repeated → CPU & Memory Exhaustion (DoS)","details":"## Summary\n\nWhen PhpSpreadsheet loads an OpenDocument Spreadsheet (`.ods`), `Ods::processTableRow()`\nreads the `table:number-rows-repeated` attribute of a `\u003ctable:table-row\u003e` 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-\u003egetAttributeNS($tableNs, 'number-rows-repeated');\n...\n// line 1151 — row materialisation loop: NO upper bound\nfor ($rowAdjust = 0; $rowAdjust \u003c $rowRepeats; ++$rowAdjust) {\n    $rID = $rowID + $rowAdjust;\n    $cell = $spreadsheet-\u003egetActiveSheet()-\u003egetCell($columnID . $rID);  // allocates a Cell\n    $cell-\u003esetValueExplicit($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 \u003e 0 && $tableColumnIndex2 \u003c= AddressRange::MAX_COLUMN_INT; --$rowRepeats2) {\n    // HAS BOUNDS CHECK: && $tableColumnIndex2 \u003c= 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 \u003e 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: `\u003ctable:table-row table:number-rows-repeated=\"5000000\"\u003e` (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\u003ctable:table table:name=\"Sheet1\"\u003e\n \u003ctable:table-row table:number-rows-repeated=\"5000000\"\u003e\n  \u003ctable:table-cell office:value-type=\"float\" office:value=\"1\"\u003e\n   \u003ctext:p\u003e1\u003c/text:p\u003e\n  \u003c/table:table-cell\u003e\n \u003c/table:table-row\u003e\n\u003c/table:table\u003e\n```\n\n**Not vulnerable (for reference - throws exception):**\n\n```xml\n\u003ctable:table table:name=\"Sheet1\"\u003e\n \u003ctable:table-row\u003e\n  \u003ctable:table-cell office:value-type=\"float\" office:value=\"1\"\n      table:number-columns-repeated=\"1000000000\"\u003e\n   \u003ctext:p\u003e1\u003c/text:p\u003e\n  \u003c/table:table-cell\u003e\n \u003c/table:table-row\u003e\n\u003c/table:table\u003e\n```\n\nThis column-repeat example throws `Column string index can not be beyond XFD` exception\n(bounded defense, already in place).","aliases":["CVE-2026-108359"],"modified":"2026-10-27T15:47:24.609424615Z","published":"2026-10-27T15:38:33Z","database_specific":{"nvd_published_at":"2026-10-22T18:17:26Z","cwe_ids":["CWE-400","CWE-789"],"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-10-27T15:38:33Z"},"references":[{"type":"WEB","url":"https://github.com/PHPOffice/PhpSpreadsheet/security/advisories/GHSA-j2w7-387v-m2rq"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-108359"},{"type":"WEB","url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/299993894dce37aa3b64aa23e9a3f4bb77ab0e66"},{"type":"WEB","url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/2de29384aa11223ee08136c0e6a9e9981bc1a2eb"},{"type":"WEB","url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/980b5c294493856f1a25754f1e8a1d2185b93738"},{"type":"WEB","url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/9a0b48622ebfe93fa48cb9bc09ce57303bb07af9"},{"type":"WEB","url":"https://github.com/PHPOffice/PhpSpreadsheet/commit/da13f40e8760d599b0e8a296017d4b028b20e86c"},{"type":"PACKAGE","url":"https://github.com/PHPOffice/PhpSpreadsheet"},{"type":"WEB","url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/1.30.7"},{"type":"WEB","url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/2.1.19"},{"type":"WEB","url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/2.4.8"},{"type":"WEB","url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/3.10.8"},{"type":"WEB","url":"https://github.com/PHPOffice/PhpSpreadsheet/releases/tag/5.10.0"}],"affected":[{"package":{"name":"phpoffice/phpspreadsheet","ecosystem":"Packagist","purl":"pkg:composer/phpoffice/phpspreadsheet"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.0.0"},{"fixed":"5.10.0"}]}],"versions":["4.0.0","4.1.0","4.2.0","4.3.0","4.3.1","4.4.0","4.5.0","5.0.0","5.1.0","5.2.0","5.3.0","5.4.0","5.5.0","5.6.0","5.7.0","5.8.0","5.8.1","5.9.0"],"database_specific":{"last_known_affected_version_range":"\u003c= 5.9.0","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-j2w7-387v-m2rq/GHSA-j2w7-387v-m2rq.json"}},{"package":{"name":"phpoffice/phpspreadsheet","ecosystem":"Packagist","purl":"pkg:composer/phpoffice/phpspreadsheet"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.3.0"},{"fixed":"3.10.8"}]}],"versions":["3.10.0","3.10.1","3.10.2","3.10.3","3.10.4","3.10.5","3.10.6","3.10.7","3.3.0","3.4.0","3.5.0","3.6.0","3.7.0","3.8.0","3.9.0","3.9.1","3.9.2","3.9.3"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-j2w7-387v-m2rq/GHSA-j2w7-387v-m2rq.json","last_known_affected_version_range":"\u003c= 3.10.7"}},{"package":{"name":"phpoffice/phpspreadsheet","ecosystem":"Packagist","purl":"pkg:composer/phpoffice/phpspreadsheet"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.2.0"},{"fixed":"2.4.8"}]}],"versions":["2.2.0","2.2.1","2.2.2","2.3.0","2.3.10","2.3.2","2.3.3","2.3.4","2.3.5","2.3.6","2.3.7","2.3.8","2.3.9","2.4.0","2.4.1","2.4.2","2.4.3","2.4.4","2.4.5","2.4.6","2.4.7"],"database_specific":{"last_known_affected_version_range":"\u003c= 2.4.7","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-j2w7-387v-m2rq/GHSA-j2w7-387v-m2rq.json"}},{"package":{"name":"phpoffice/phpspreadsheet","ecosystem":"Packagist","purl":"pkg:composer/phpoffice/phpspreadsheet"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.0.0"},{"fixed":"2.1.19"}]}],"versions":["2.0.0","2.1.0","2.1.1","2.1.10","2.1.11","2.1.12","2.1.13","2.1.14","2.1.15","2.1.16","2.1.17","2.1.18","2.1.3","2.1.4","2.1.5","2.1.6","2.1.7","2.1.8","2.1.9"],"database_specific":{"last_known_affected_version_range":"\u003c= 2.1.18","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-j2w7-387v-m2rq/GHSA-j2w7-387v-m2rq.json"}},{"package":{"name":"phpoffice/phpspreadsheet","ecosystem":"Packagist","purl":"pkg:composer/phpoffice/phpspreadsheet"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.30.7"}]}],"versions":["1.0.0","1.0.0-beta","1.0.0-beta2","1.1.0","1.10.0","1.10.1","1.11.0","1.12.0","1.13.0","1.14.0","1.14.1","1.15.0","1.16.0","1.17.0","1.17.1","1.18.0","1.19.0","1.2.0","1.2.1","1.20.0","1.21.0","1.22.0","1.23.0","1.24.0","1.24.1","1.25.0","1.25.1","1.25.2","1.26.0","1.27.0","1.27.1","1.28.0","1.29.0","1.29.1","1.29.10","1.29.11","1.29.12","1.29.2","1.29.4","1.29.5","1.29.6","1.29.7","1.29.8","1.29.9","1.3.0","1.3.1","1.30.0","1.30.1","1.30.2","1.30.3","1.30.4","1.30.5","1.30.6","1.4.0","1.4.1","1.5.0","1.5.1","1.5.2","1.6.0","1.7.0","1.8.0","1.8.1","1.8.2","1.9.0"],"database_specific":{"last_known_affected_version_range":"\u003c= 1.30.6","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-j2w7-387v-m2rq/GHSA-j2w7-387v-m2rq.json"}}],"schema_version":"1.9.2","severity":[{"type":"CVSS_V4","score":"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"}]}