GitHub Advisoryadvisory · Go

GHSA-q5j5-6p94-4gwc Excelize: Streaming GetRows row-bound bypass causes attacker-controlled allocation

HIGH Published 2026-09-10 Modified 2026-09-10 Official page ↗
Aliases
CVE-2026-59161
Ecosystem
Go
Packages
github.com/xuri/excelize/v2, github.com/xuri/excelize
Severity
High
CVSS vector
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
Weaknesses
CWE-400, CWE-770
Reviewed
2026-09-10

Affected packages

• Go github.com/xuri/excelize/v2: < 2.11.0 → fixed in 2.11.0 • Go github.com/xuri/excelize: <= 1.4.0 (no fix yet)

Description

Streaming GetRows row-bound bypass causes attacker-controlled allocation Summary Excelize's prior row-bound fix for GHSA-h69g / CVE-2026-54063 protects the checked worksheet parser, but the streaming worksheet reader used by `Rows` and `GetRows` does not enforce the same `TotalRows` bound on the row `r` attribute. A small XLSX file can set a row number above Excelize's maximum row (`1048576`) and omit the cell coordinate. `GetRows` then appends empty rows up to the attacker-controlled row index and returns success. This was reproduced on the current default branch commit `1213a8bd7c5ab360554603ac5c995ccaf6eb4314` and the latest release tag `v2.10.1` (`5ad5ab3af0054c55bdce09f1530085600e9f2e45`). Affected package • Package: `github.com/xuri/excelize/v2` • Tested affected versions: current default branch at `1213a8bd7c5ab360554603ac5c995ccaf6eb4314`, and release `v2.10.1` • Fixed version: none known at the time of this report Impact An attacker who can provide an XLSX file to an application that calls `GetRows` can cause memory and CPU usage to scale with an attacker-controlled row number, even though the file itself is tiny. This is an availability issue and appears to be an incomplete coverage variant of the GHSA-h69g row-index allocation class. In the conservative PoC, `row r="2000000"` returned a `[][]string` with length 2,000,000 and allocated about 46 MB. Larger row numbers scale the allocation further. Root cause The checked parser path validates row numbers: • `excelize.go`: `checkRowNum(r int)` rejects negative rows and rows greater than `TotalRows`. • `excelize.go`: `checkSheet()` calls `checkRowNum(r.R)` before allocating sheet rows. • `workSheetReader()` invokes `checkSheet()` / `checkRow()` before returning a cached worksheet. The streaming path does not use that checked parser: • `rows.go`: `Rows(sheet)` opens an XML decoder directly. • `Rows.Next()` accepts the row `r` attribute and assigns it to the iterator's current row without applying `checkRowNum()`. • `Rows.Columns()` also assigns row `r` to the iterator state without applying `checkRowNum()`. • `GetRows()` appends empty row slices for the gap between the previous row and the current row. Because a cell without an `r` coordinate can still contain a value, the worksheet can avoid cell-coordinate row validation while still causing `GetRows()` to materialize rows up to the out-of-range row number. Minimal worksheet payload ```xml 0 ``` The workbook also contains a normal `sharedStrings.xml` with one string (`ok`). Reproduction A minimal Go harness creates the XLSX in memory and calls `GetRows("Sheet1")`: ```go rows, err := f.GetRows("Sheet1") fmt.Println("rows_len:", len(rows)) if len(rows) > 0 { fmt.Println("last_row:", rows[len(rows)-1]) } fmt.Printf("returned error: %T %v\n", err, err) ``` Observed output on current default branch commit `1213a8bd7c5ab360554603ac5c995ccaf6eb4314`: ```text == streaming GetRows row r=2000000 cell without r == rows_len: 2000000 last_row: [ok] returned error: elapsed=21ms alloc_delta=46MB ``` Observed output on latest release tag `v2.10.1`: ```text == streaming GetRows row r=2000000 cell without r == rows_len: 2000000 last_row: [ok] returned error: elapsed=14ms alloc_delta=46MB ``` A control using the checked parser with `row r="1048577"` and `c r="A1048577"` correctly returns `row number exceeds maximum limit`, confirming this report is about inconsistent enforcement in the streaming path rather than a missing global constant. Expected behavior `Rows` / `GetRows` should reject row numbers greater than `TotalRows` with the same error behavior as the checked parser path. Suggested remediation • Apply the same row-bound validation in the streaming reader immediately after parsing a row `r` attribute. • Preserve and return row parsing errors from `GetRows()` instead of silently continuing or returning only `Rows.Close()` errors. • Add regression tests for `GetRows()` on a worksheet containing `row r="1048577"` with a cell value but no cell `r` coordinate.

Related

Alias · CVE (1)

  • CVE-2026-59161 Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. vulnerability

Weakness · CWE (2)

  • CWE-400 Uncontrolled Resource Consumption weakness
  • CWE-770 Allocation of Resources Without Limits or Throttling weakness

References