GHSA-WP2G-VPJJ-G53R
Vulnerability from github – Published: 2026-10-08 16:31 – Updated: 2026-10-08 16:31Summary
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.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/xuri/excelize/v2"
},
"ranges": [
{
"events": [
{
"introduced": "2.8.1"
},
{
"fixed": "2.11.1-0.20260911060113-ea12859e43c6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-107216"
],
"database_specific": {
"cwe_ids": [
"CWE-674"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-08T16:31:21Z",
"nvd_published_at": "2026-10-07T18:17:19Z",
"severity": "HIGH"
},
"details": "### Summary\n\n`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` \u2014 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*.\n\nDuring 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:\n\n- `A1` (dynamic array formula, ref `A1:A1`): `_xlfn.ANCHORARRAY($B$1)`\n- `B1` (dynamic array formula, ref `B1:B1`): `_xlfn.ANCHORARRAY($A$1)`\n\n\u2192 `CalcCellValue \u2192 calcCellValue \u2192 evalInfixExp \u2192 parseReference \u2192 cellResolver \u2192 \u2026 \u2192 ANCHORARRAY \u2192 CalcCellValue \u2192 \u2026` forever, ending in a **fatal, unrecoverable** Go runtime error: `runtime: goroutine stack exceeds \u2026-byte limit / fatal error: stack overflow`. Go stack overflows cannot be recovered \u2014 the whole process aborts.\n\n### Details\n\n- The terminating edge the iterations gate is supposed to provide does not exist across contexts: there is no in-flight tracking shared between nested `CalcCellValue` calls.\n- Both formulas are ordinary Excel-legal constructs; no exotic XML is required.\n- Excelize\u0027s 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.\n- No option value prevents it: `MaxCalcIterations` is irrelevant because every hop gets a fresh budget.\n- Measured: a 64 MB stack budget is exhausted in ~0.09 s; the ~1 GB default in ~1\u20132 s.\n\n### PoC\n\nA 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.\n\n### Impact\n\nAn attacker ships a workbook containing the two formulas; any service that evaluates a formula over those cells \u2014 directly via `CalcCellValue`, or implicitly via `AddPivotTable` / `AddPicture` / auto-fit \u2014 aborts. Remote, unauthenticated, process-fatal, no configuration prevents it.\n\n### Proposed fix\n\nEvaluate spill-range cells through the **current** calculation context \u2014 `fn.f.cellResolver(fn.ctx, \u2026)` instead of the exported `CalcCellValue` \u2014 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 \u2192 \"\"` 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.",
"id": "GHSA-wp2g-vpjj-g53r",
"modified": "2026-10-08T16:31:21Z",
"published": "2026-10-08T16:31:21Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/qax-os/excelize/security/advisories/GHSA-wp2g-vpjj-g53r"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-107216"
},
{
"type": "WEB",
"url": "https://github.com/qax-os/excelize/commit/ea12859e43c64d498ecf839263c5f917391f3316"
},
{
"type": "PACKAGE",
"url": "https://github.com/qax-os/excelize"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Excelize ANCHORARRAY: mutually-referencing array formulas recurse unboundedly via re-entrant CalcCellValue, causing a fatal stack overflow"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.