Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days
### Summary A worksheet whose `` number is far past Excel's 1,048,576-row limit makes `File.GetRows` and the `Rows` iterator loop once for every missing row. The row limit is checked in `Rows.Next`, but not in `Rows.Columns`, which also reads `` elements. A 1.5 KB file with `` after an ordinary first row keeps `GetRows` busy for an estimated 11 days (about 4 ns per missing row), using one CPU core and little memory. Any service that calls `GetRows` or iterates `Rows` on an uploaded workbook can be tied up by a single request. ### Details `Rows.Next` checks the row number: ```go rowNum, _ := attrValToInt("r", xmlElement.Attr) if rowNum > TotalRows { rows.err = ErrMaxRows return false } ``` But `Rows.Columns` reads the cells of the current row by consuming tokens until it reaches the *next* `` element, and it sets `rows.curRow` from that element's `r` without the check (rows.go, around line 179 at 3985c1f): ```go if rowNum, rowIterator.err = attrValToInt("r", xmlElement.Attr); rowNum != 0 { rows.curRow = rowNum } ``` After that, `rows.curRow` is 231999999999940, and each later `Next()` call takes the `rows.curRow >= rows.seekRow` shortcut and returns `true` without reading any XML. `GetRows` then calls `Next()` and `Columns()` once per row number from 2 to 231999999999940. The check in `Next()` never runs, because the oversized `` element was already consumed by `Columns()`. If the oversized row is the *first* row, `Next()` reads it and the existing check works; `TestGetRows` c
- Products
- github.com/xuri/excelize/v2
- Affected versions
- Not specified
- Fixed versions
- 2.11.1-0.20260930021559-01a9ff32fb3c
- CVEs
- CVE-2026-107212
- Confirmed fixed CVEs
- Not specified
- Updated in this feed
- 31 Oct 2026, 08:43 UTC · Revision 1
Source evidence
Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days
core-osv · Original sourcePublished 8 Oct 2026, 16:50 UTC
### Summary A worksheet whose `` number is far past Excel's 1,048,576-row limit makes `File.GetRows` and the `Rows` iterator loop once for every missing row. The row limit is checked in `Rows.Next`, but not in `Rows.Columns`, which also reads `` elements. A 1.5 KB file with `` after an ordinary first row keeps `GetRows` busy for an estimated 11 days (about 4 ns per missing row), using one CPU core and little memory. Any service that calls `GetRows` or iterates `Rows` on an uploaded workbook can be tied up by a single request. ### Details `Rows.Next` checks the row number: ```go rowNum, _ := attrValToInt("r", xmlElement.Attr) if rowNum > TotalRows { rows.err = ErrMaxRows return false } ``` But `Rows.Columns` reads the cells of the current row by consuming tokens until it reaches the *next* `` element, and it sets `rows.curRow` from that element's `r` without the check (rows.go, around line 179 at 3985c1f): ```go if rowNum, rowIterator.err = attrValToInt("r", xmlElement.Attr); rowNum != 0 { rows.curRow = rowNum } ``` After that, `rows.curRow` is 231999999999940, and each later `Next()` call takes the `rows.curRow >= rows.seekRow` shortcut and returns `true` without reading any XML. `GetRows` then calls `Next()` and `Columns()` once per row number from 2 to 231999999999940. The check in `Next()` never runs, because the oversized `` element was already consumed by `Columns()`. If the oversized row is the *first* row, `Next()` reads it and the existing check works; `TestGetRows` c
osv:GHSA-jw42-f3rr-4cc3
github.com/xuri/excelize/v2
Fixed versions: 2.11.1-0.20260930021559-01a9ff32fb3c
Complete source record
{
"id": "e231ce35d10c7e7f2c1f76b90e881b06823c6d952c33e09e7ae22b490921edee",
"url": "https://osv.dev/vulnerability/GHSA-jw42-f3rr-4cc3",
"method": "structured",
"payload": {
"kind": "advisory",
"title": "Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days",
"summary": "### Summary A worksheet whose `` number is far past Excel's 1,048,576-row limit makes `File.GetRows` and the `Rows` iterator loop once for every missing row. The row limit is checked in `Rows.Next`, but not in `Rows.Columns`, which also reads `` elements. A 1.5 KB file with `` after an ordinary first row keeps `GetRows` busy for an estimated 11 days (about 4 ns per missing row), using one CPU core and little memory. Any service that calls `GetRows` or iterates `Rows` on an uploaded workbook can be tied up by a single request. ### Details `Rows.Next` checks the row number: ```go rowNum, _ := attrValToInt(\"r\", xmlElement.Attr) if rowNum > TotalRows { rows.err = ErrMaxRows return false } ``` But `Rows.Columns` reads the cells of the current row by consuming tokens until it reaches the *next* `` element, and it sets `rows.curRow` from that element's `r` without the check (rows.go, around line 179 at 3985c1f): ```go if rowNum, rowIterator.err = attrValToInt(\"r\", xmlElement.Attr); rowNum != 0 { rows.curRow = rowNum } ``` After that, `rows.curRow` is 231999999999940, and each later `Next()` call takes the `rows.curRow >= rows.seekRow` shortcut and returns `true` without reading any XML. `GetRows` then calls `Next()` and `Columns()` once per row number from 2 to 231999999999940. The check in `Next()` never runs, because the oversized `` element was already consumed by `Columns()`. If the oversized row is the *first* row, `Next()` reads it and the existing check works; `TestGetRows` c",
"evidence": "osv:GHSA-jw42-f3rr-4cc3",
"products": [
{
"name": {
"value": "github.com/xuri/excelize/v2",
"evidence": "/affected/0/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "2.11.1-0.20260930021559-01a9ff32fb3c",
"evidence": "/affected/0/ranges"
}
],
"affected_versions": []
}
],
"severity": "high",
"structured": {
"id": "GHSA-jw42-f3rr-4cc3",
"aliases": [
"CVE-2026-107212",
"GHSA-jw42-f3rr-4cc3",
"GO-2026-6685"
],
"metrics": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
}
],
"affected": [
{
"ranges": [
{
"type": "SEMVER",
"events": [
{
"introduced": "2.1.0"
},
{
"fixed": "2.11.1-0.20260930021559-01a9ff32fb3c"
}
]
}
],
"package": {
"name": "github.com/xuri/excelize/v2",
"purl": "pkg:golang/github.com/xuri/excelize/v2",
"ecosystem": "Go"
},
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-jw42-f3rr-4cc3/GHSA-jw42-f3rr-4cc3.json"
}
}
],
"modified": "2026-10-19T21:40:42.350043538Z",
"provider": "osv",
"references": [
"https://github.com/qax-os/excelize/security/advisories/GHSA-jw42-f3rr-4cc3",
"https://nvd.nist.gov/vuln/detail/CVE-2026-107212",
"https://github.com/qax-os/excelize/pull/2438",
"https://github.com/qax-os/excelize/commit/01a9ff32fb3c1f873cf01205e1b8a3285b0e1d23",
"https://github.com/qax-os/excelize"
],
"provider_metadata": {
"cwe_ids": [
"CWE-770"
],
"severity": "HIGH",
"github_reviewed": true,
"nvd_published_at": "2026-10-07T18:17:18Z",
"github_reviewed_at": "2026-10-08T16:50:13Z"
}
},
"related_cves": [
{
"value": "CVE-2026-107212",
"evidence": "/aliases"
}
],
"security_relevant": true,
"severity_evidence": "/database_specific/severity"
},
"source_id": "core-osv",
"document_id": 376729,
"published_at": "2026-10-08T16:50:13+00:00"
}