GHSA-mx22-3794-2vpv: Out-of-bounds Read

Published Oct 8, 2026
·
Updated

Summary

extractPivotTableFields builds order := pc.getPivotCacheFieldsName() from xl/pivotCache/pivotCacheDefinitionN.xml's <cacheFields> list, then indexes it with values taken from a separately parsed xl/pivotTables/pivotTableN.xml part with zero cross-validation: order[field.Fld] where field.Fld is a raw attacker-controlled int from the <dataField fld="N"> attribute, and order[fieldIdx] where fieldIdx is a loop index over pt.PivotFields.PivotField that need not match the cache's field count. I executed two independent PoCs against the current HEAD: (1) trimmed the pivot cache's <cacheFields> to count=0 while leaving the pivot table's 2 pivotFields untouched -> runtime error: index out of range [0] with length 0 at pivotTable.go:1431; (2) left the 2 cache fields completely intact and only changed one attribute, fld="1" -> fld="99999", in xl/pivotTables/pivotTable1.xml -> runtime error: index out of range [99999] with length 2 at pivotTable.go:1443. Both stack traces confirmed via non-recovered go test runs: extractPivotTableFields -> getPivotTable -> GetPivotTables.

Reachability / who can trigger this

Unauthenticated: any application that opens an untrusted .xlsx containing a pivot table and calls the public File.GetPivotTables(sheet) API. excelize has no recover() anywhere, so this crashes the host process (CLI/worker) or surfaces as an unhandled 500 in a request-scoped-recover HTTP service. Deterministic, single small crafted file, no user interaction beyond the service's normal open-and-read flow.

Proof of Concept / Reproduction

Method: 1) Verified source at HEAD (commit e81f0500, 2026-09-27, freshly cloned/up to date; git status clean except the new PoC files I added): read pivotTable.go:1425-1454 and confirmed verbatim: order := pc.getPivotCacheFieldsName() (1428), loop for fieldIdx, field := range pt.PivotFields.PivotField indexing order[fieldIdx] at axisRow/axisCol/axisPage (1431/1434/1437), and Data: order[field.Fld] (1443) inside the pt.DataFields.DataField loop -- zero bounds checks anywhere. Confirmed xmlPivotTable.go:271 Fld int xml:"fld,attr" on xlsxDataField (raw attacker-controlled int, no validation) and the sibling Fld field at line 252. Ran go build ./... -- compiles clean. Both citations are accurate. 2) Found the repo already contained an untracked PoC test file from earlier work (pivotfldpoctest.go) implementing this exact finding via a byte-level zip-tamper technique (build a legitimate workbook with excelize's own public AddPivotTable API, then hex-edit one raw XML part inside the saved .xlsx's zip bytes -- not a hypothetical reimplementation, uses the real unmodified excelize package). Did not just trust it: ran it myself with go test -run 'TestPivotTableDataFieldFldIndexPanic|TestPivotTableDataFieldFldAttributeOutOfRangePanic' -v . and observed real PASS output with exact matching panic strings. 3) For stronger, independent evidence beyond a recover-wrapped test, I wrote my own standalone non-test Go program from scratch, cmd/pivotcrash/main.go (module github.com/xuri/excelize/v2, go1.27.0 toolchain), that imports the real compiled excelize package (no reimplementation) with two subcommands: gen/gen-trim (attacker: build a legit pivot-table workbook via the public API, then rezip with one XML part tampered -- either dataField fld="1"->fld="99999", or the pivot cache's <cacheFields count="2"> collapsed to count="0" -- writing the crafted .xlsx to disk) and run (victim: a fresh OS process that does exactly what any consumer service does -- excelize.OpenFile(path) then f.GetPivotTables("Sheet1") -- with zero recover() anywhere in the file or the call chain). Executed as four separate go run process invocations: gen, run, gen-trim, run.

Evidence: Test run (pre-existing PoC file, re-executed by me, both PASS): --- PASS: TestPivotTableDataFieldFldIndexPanic (0.00s) logging CONFIRMED: GetPivotTables panicked ... runtime error: index out of range [0] with length 0 --- PASS: TestPivotTableDataFieldFldAttributeOutOfRangePanic (0.00s) logging CONFIRMED: GetPivotTables panicked ... runtime error: index out of range [99999] with length 2 ok github.com/xuri/excelize/v2 0.114s

Standalone process-crash run #1 (go run ./cmd/pivotcrash run pivotcrashmalicious.xlsx, fld="99999" tamper, cache untouched with 2 fields): [victim] file opened successfully, workbook parsed with no errors [victim] now calling f.GetPivotTables("Sheet1") on the untrusted workbook... panic: runtime error: index out of range [99999] with length 2 goroutine 1 [running]: github.com/xuri/excelize/v2.(File).extractPivotTableFields(...) .../pivotTable.go:1443 +0xb9b github.com/xuri/excelize/v2.(File).getPivotTable(...) .../pivotTable.go:1379 +0x7f6 github.com/xuri/excelize/v2.(File).GetPivotTables(...) .../pivotTable.go:1278 +0x2f7 main.runVictim(...) / main.main() -> exit status 2

Standalone process-crash run #2 (go run ./cmd/pivotcrash run pivotcrashmalicioustrim.xlsx, cacheFields trimmed to count=0, pivot table's 2 pivotFields untouched): panic: runtime error: index out of range [0] with length 0 goroutine 1 [running]: github.com/xuri/excelize/v2.(File).extractPivotTableFields(...) .../pivotTable.go:1431 +0xc15 github.com/xuri/excelize/v2.(File).getPivotTable(...) .../pivotTable.go:1379 +0x7f6 github.com/xuri/excelize/v2.(File).GetPivotTables(...) .../pivotTable.go:1278 +0x2f7 main.runVictim(...) / main.main() -> exit status 2

In both standalone runs the "GetPivotTables RETURNED NORMALLY" print statement that follows the call was never reached -- the OS process itself terminated via an unrecovered Go panic (exit status 2), for a crafted .xlsx that excelize.OpenFile accepted without any error. Both stack traces terminate at the exact claimed sink lines (pivotTable.go:1443 and :1431) via the exact claimed call chain (extractPivotTableFields -> getPivotTable -> GetPivotTables). This is a real, unhandled crash of a real process running unmodified excelize code -- not a mock, not a simulated/hypothetical trace.

Novelty

Ran all 5 requested checks in full, plus extra verification. (1) Pulled the complete, paginated GHSA list via gh api (16 advisories, all state=published, no hidden drafts) and grepped full descriptions, not just summaries, for "pivot" -- only one incidental, non-overlapping hit (GHSA-wp2g-vpjj-g53r cites pivotTable.go:538 as an AddPivotTable call site for an unrelated formula-recursion stack-overflow bug). (2) Ran ~15 targeted GitHub issue searches (extractPivotTableFields, getPivotCacheFieldsName, GetPivotTables panic, dataField fld, BaseField, ShowValuesAs, "index out of range", cacheFields count mismatch, PivotFields panic, DataField, plus a broad "pivot" query returning 30 issues/PRs all individually triaged by title) and full-text-read the 5 most plausible (#2161, #1937, #2183, #1954, #1945) -- none match; the closest (#2161) is a nil-pointer bug in a different function/field, detailed above. (3) Checked PRs both by keyword (is:pr pivot = 10, is:pr "pivotTable.go" fld = 0, all reviewed; read PR #2168's full diff) and exhaustively (pulled all 31 currently-open PRs on the repo and reviewed every title) -- none touch pivot field indexing. (4) Ran 5 WebSearch queries combining the mechanism with "excelize"/"pivot table"/"CVE", and independently cross-checked OSV.dev's API (which mirrors NVD/GHSA/GO vuln DB) -- turned up only the already-ruled-out GHSA-fx5j (shared-string) and GHSA-h69g (row allocation) advisories, and a non-upstream automated "task"-tracking repo (windyswe/q ...[truncated for brevity]

Closest prior art considered: qax-os/excelize issue #2161 + merged PR #2168 ("This closes #2161, fix panic on read unsupported pivot table cache source types", merged 2025-07-05). That bug: pivotCacheDefinition.xml's <cacheSource type="external"/> has no <worksheetSource> child, so pc.CacheSource.WorksheetSource is a nil pointer, and getPivotTable() (then pivotTable.go:875) dereferenced .Sheet/.Ref on it -> nil-pointer-dereference panic, reached via GetPivotTables (then line 803). Fix was an early type-validation error return in getPivotTable, not any bounds check. SAME-FIX TEST: would that fix also close the candidate bug? No. The candidate's panic is index-out-of-range (not nil-deref) inside a different function, extractPivotTableFields, on a different data structure: order := pc.getPivotCacheFieldsName() (length = count of <cacheFields>) indexed by (a) a loop counter over pt.PivotFields.PivotField (a separate, unrelated count from pivotTableN.xml) at lines 1431/1434/1437, and (b) the raw unvalidated int field. ...[truncated for brevity]

Independent skeptical review

Tried hard to kill this and it holds. Independently re-read current HEAD (e81f05008b3, fetched and diffed against origin/master to confirm it's current) rather than trusting the report: pivotTable.go:1427-1454 (extractPivotTableFields) has zero bounds checks on order[fieldIdx] (axisRow/Col/Page branches, lines 1431/1434/1437) or order[field.Fld] (line 1443) — confirmed by direct Read, matching the claim's line citations exactly. This is a genuine oversight, not a designed invariant: the very next function in the same file, extractPivotTableShowValuesAs (lines 1476, 1486) and extractPivotTableField (line 1508), DO bounds-check structurally identical order/slice indices before using them — the developers clearly know this pattern needs guarding and simply missed it in extractPivotTableFields. Confirmed xmlPivotTable.go:271 Fld is a bare unchecked int XML attribute, and confirmed getPivotTable/pivotCacheReader/pivotTableReader perform no count-vs-actual-length cross-validation between the independently-parsed pivotTable and pivotCache XML parts. Grepped the entire repo for recover() — zero hits outside the researcher's own test file, confirming "no recover anywhere" is accurate. Re-ran both provided PoCs myself in a throwaway test file against this exact HEAD (go test -run TestPoCPivotCache -v): both panic exactly as claimed, with byte-identical messages — "index out of range [0] with length 0" (truncated-cache-fields PoC, line 1431) and "index out of range [99999] with lengt ...[truncated for brevity]

Affected Software

1 affected componentFixes available
go/github.com/xuri/excelize/v2>=2.8.1<2.11.1-0.20261003002531-6258dcebc4e2
2.11.1-0.20261003002531-6258dcebc4e2

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade go/github.com/xuri/excelize/v2 to a version that resolves this vulnerability.

    Fixed in 2.11.1-0.20261003002531-6258dcebc4e2

Event History

Oct 8, 2026
Advisory Published
via GitHub·05:52 PM
Data Sourced
via GitHub·05:52 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

Who is exposed to this issue?

Applications using go/github.com/xuri/excelize/v2 are exposed when they open attacker-controlled Excel workbooks and invoke GetPivotTables. The described trigger does not require authentication.

2

What does an attacker need to provide to trigger the failure?

An attacker needs to supply a workbook with inconsistent pivot-table metadata. Examples include a pivot table with more pivotFields than the pivot cache has cacheFields, or a dataField fld attribute that is larger than the available cache-field list.

3

What is the likely impact during processing?

The supplied cases cause an unrecovered Go runtime "index out of range" error while extracting pivot-table fields. The observed call path is extractPivotTableFields through getPivotTable and GetPivotTables.

4

How can I determine whether an application is affected?

Test the application's workbook-processing path with a workbook containing pivot tables, especially malformed or inconsistent pivot cache and pivot table XML. An affected path may terminate with an index-out-of-range error in pivotTable.go while GetPivotTables is called.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203