Vulnerability GHSA-wp2g-vpjj-g53r
Summary
Excelize ANCHORARRAY: mutually-referencing array formulas recurse unboundedly via re-entrant CalcCellValue, causing a fatal stack overflow
Details
Summary
ANCHORARRAY (calc.go:15137 on current master) evaluates each cell of the referenced spill range by calling the exported CalcCellValue, which unconditionally constructs a fresh calcContext — fresh entry marker, fresh iterations map, full MaxCalcIterations budget (calc.go:896-900). The circular-reference control only exists within one context: the entry-exclusion marker and per-ref iteration budget are fields of that single context, and completion-based caching (formulaArgCache/calcRawCache) only ever stores results of evaluations that finish.
During a pure cycle no nested evaluation ever finishes, so nothing is ever cached to break the recursion, and each hop re-arms the entire budget. Two ordinary, Excel-legal constructs in an attacker-supplied workbook are enough:
A1(dynamic array formula, refA1:A1):_xlfn.ANCHORARRAY($B$1)B1(dynamic array formula, refB1:B1):_xlfn.ANCHORARRAY($A$1)
→ CalcCellValue → calcCellValue → evalInfixExp → parseReference → cellResolver → … → ANCHORARRAY → CalcCellValue → … forever, ending in a fatal, unrecoverable Go runtime error: runtime: goroutine stack exceeds …-byte limit / fatal error: stack overflow. Go stack overflows cannot be recovered — the whole process aborts.
Details
- The terminating edge the iterations gate is supposed to provide does not exist across contexts: there is no in-flight tracking shared between nested
CalcCellValuecalls. - Both formulas are ordinary Excel-legal constructs; no exotic XML is required.
- Excelize's own APIs reach formula evaluation on untrusted cells implicitly (e.g.
pivotTable.go:538,picture.go:985/1141,col.go:892), so a service that merely adds a pivot table or a picture over such a workbook dies. - No option value prevents it:
MaxCalcIterationsis irrelevant because every hop gets a fresh budget. - Measured: a 64 MB stack budget is exhausted in ~0.09 s; the ~1 GB default in ~1–2 s.
PoC
A standalone program (public API only) was provided to the maintainer by email (4-anchorarray-recursion): NewFile + SetCellFormula with FormulaOpts{Type: array, Ref: A1:A1 / B1:B1}, then CalcCellValue("Sheet1","A1"). On master ecd99d761fe0 (2026-09-08) the process aborts with fatal error: stack overflow; with the proposed patch the cycle terminates normally (CYCLE_TERMINATED) and existing calc tests pass.
Impact
An attacker ships a workbook containing the two formulas; any service that evaluates a formula over those cells — directly via CalcCellValue, or implicitly via AddPivotTable / AddPicture / auto-fit — aborts. Remote, unauthenticated, process-fatal, no configuration prevents it.
Proposed fix
Evaluate spill-range cells through the current calculation context — fn.f.cellResolver(fn.ctx, …) instead of the exported CalcCellValue — so the entry check and iterations gate of the running calculation apply. cellResolver returns the typed value directly (dropping a string round-trip); an ArgEmpty → "" shim preserves the existing ToNumber behavior for empty spill cells, and a fresh-context fallback covers the legacy nil-ctx test paths. A complete patch has been provided to the maintainer.
Related Vulnerabilities
Other vulnerabilities affecting the same packages