GHSA-8mcq-6wmr-jrjv
Excelize: a row whose earlier cell has a higher column reference than its last cell panics index out of range on almost every worksheet read API
Packages
| Ecosystem | Package | Affected | Fixed in |
|---|---|---|---|
| Go | github.com/xuri/excelize/v2 | >= 2.0.0, < 2.11.1-0.20260816084418-46a5eb289448 | 2.11.1-0.20260816084418-46a5eb289448 |
Details
As written in the advisory (Markdown source, shown as plain text).
## Affected versions and vulnerable location
- Confirmed at `ae2113b` (current HEAD).
- Sink: `rows.go:967` `ws.SheetData.Row[rowIdx].C[colNum-1] = *colData` inside `checkRow`.
- `checkRow` computes `lastCol` from the column of the last cell in document order (`rows.go:940`), allocates `targetList` of that length, then re-scatters every source cell into `C[colNum-1]`.
## Root cause
The slice is sized from the last cell's column, but cells are not required to be column-sorted in the XML. A cell that appears earlier in the row but references a higher column than the last cell has `colNum-1 >= len(targetList)`, so the assignment writes out of range. `MaxColumns`/`TotalRows` do not help, every individual column is valid; the bug is the ordering assumption, not magnitude.
## Attacker model and reachability
Any service that opens an untrusted spreadsheet and calls a worksheet API that goes through `workSheetReader -> checkRow` (`excelize.go:332`): `GetCellValue`, `GetCellFormula`, `CalcCellValue`, `GetMergeCells`, `SetCellValue`, and essentially every non-streaming worksheet call. (The streaming `GetRows`/`Rows()` SAX path does not trigger it.) Unauthenticated, deterministic, unrecovered panic -> process crash.
## Proof of concept (executed)
Crafted `xl/worksheets/sheet1.xml` with a row whose cells are out of column order and whose earlier cell exceeds the last cell's column:
```xml
<row r="1"><c r="D1"><v>4</v></c><c r="C1"><v>3</v></c></row>
```
`GetCellValue("Sheet1","A1")` (via `getCellStringFunc -> workSheetReader -> checkRow`) panicked `index out of range [3] with length 3` at `rows.go:967`.
Confirming grep:
```bash
rg -n "func checkRow|lastCol|Row\[rowIdx\].C\[colNum-1\]" rows.go
```
## Suggested fix
Size `targetList` from the maximum cell column in the row (not the last cell in document order), or bounds-check `colNum-1` against `len(targetList)` and grow the slice as needed before the assignment.
References
External links from the advisory. They lead to third-party sites.