extractPivotTableFields builds order := pc.getPivotCacheFieldsName() from xl/pivotCache/pivotCacheDefinitionN.xml's 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 to count=0 …
A worksheet whose <row r="…"> number is far past Excel's 1,048,576-row limit makes File.GetRows and the Rows iterator loop once for every missing row. The row limit is checked in Rows.Next, but not in Rows.Columns, which also reads <row> elements. A 1.5 KB file with <row r="231999999999940"> after an ordinary first row keeps GetRows busy for an estimated 11 days (about 4 ns per missing row), using one CPU core …
GetSlicers guards only if ws.ExtLst == nil { return } (slicer.go:816-818) and then immediately dereferences ws.Drawing.RID with no nil check on ws.Drawing itself. Drawing and ExtLst are two independently optional child elements of in xl/worksheets/sheetN.xml, populated separately by encoding/xml depending on whether each tag is present. I executed a PoC: took a plain NewFile() workbook (no drawing, no slicer), saved it, then injected a bare <extLst></extLst> immediately before </worksheet> in …
extractPart allocates directly from the mscfb directory-entry size with no clamping (crypt.go:198, same missing bound at crypt.go:204): buf := make([]byte, entry.Size) entry.Size is the raw streamSize field from the attacker-controlled CFB directory entry (mscfb v1.0.8 fixFile, file.go:109-120: full uint64 → int64 for major version 4, low uint32 for v3). mscfb only detects short sector chains later, inside File.Read (file.go:485-489 "emergency brake") — i.e. after the allocation. The zip path already …
Any file whose first 8 bytes are the OLE compound-file signature (D0 CF 11 E0 A1 B1 1A E1) is routed by OpenFile/OpenReader/OpenBytes → openReaderAt → Decrypt. The version dispatch only guarantees len(EncryptionInfo) >= 4 before handing attacker-controlled EncryptionInfo/EncryptedPackage buffers to standardDecrypt/agileDecrypt, and no callee validates structure. Malformed but version-valid content therefore fails as an unrecovered runtime panic instead of an error, terminating the calling process.
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 …
Opening a file whose first eight bytes are the OLE magic number sends excelize down the decryption path whether or not the caller set a password, or supports encrypted workbooks at all. openReaderAt (excelize.go:198-215) branches on the header alone, and agileDecrypt calls convertPasswdToKey before the verifier hash is checked, so the key-derivation loop runs spinCount times regardless. spinCount (crypt.go:98) is a plain int filled by a bare xml.Unmarshal of the …
The max attribute of a <col> element in xl/worksheets/sheetN.xml is read verbatim on open with no check against MaxColumns, and flatCols() then walks Min..Max doing a deepcopy.Copy and append per iteration. Executed: max="300000", only about 18x past the real 16384 limit, cost 46.2 s of CPU and 122 MB on a single SetColWidth call, and the growth is linear in max up to 2^31-1. Confirmed at ae2113b (HEAD at the …
leftRight (calc.go:14236) tests the length with countUTF16String, which counts a rune above U+FFFF as 2. The RIGHT branch on 14241 then slices []rune(text) at utf8.RuneCountInString(text)-numChars, which counts it as 1. For text with N such runes the two measures are 2N and N, so any numChars between them passes the guard and gives a negative index. The numChars < 0 check at 14216 does not help, since the value that …
A worksheet containing <mergeCell ref=""/> makes mergeCellsParser leave the cached rectangle empty, and cellInRange then indexes that empty slice without a length check. Every non-streaming cell API panics with index out of range [0] with length 0 on the first cell read after opening the file.
File.GetStyle indexes the fill, border and font tables with values taken straight out of xl/styles.xml, and the conditions gating those lookups check only the upper bound. A workbook whose cellXfs entry carries fillId="-1", borderId="-1" or fontId="-1" reaches a negative slice index and panics. excelize has no recover(), so the panic leaves GetStyle and takes the calling process with it. Same defect class as GHSA-48hm-4h8j-58fg, the negative shared-string index, in a …
GetConditionalFormats reads sub-elements of a <cfRule> straight out of xl/worksheets/sheetN.xml and indexes them without checking length, and in one case without checking for nil. Three rule types are affected: cellIs, dataBar and colorScale. A workbook with a rule that is missing a child a real Excel file would always have panics the call.
A Zip64 uncompressed-size of 2^63 casts to a negative int64 that bypasses the unzip-size guard and reaches make([]byte, 0, negativeCap) A Zip64 uncompressed-size in the range [2^63, 2^64) casts to a negative int64 in zip.File.FileInfo().Size(), and ReadZipReader does signed arithmetic on that value, so the total-decompression guard is bypassed and the negative size reaches make([]byte, 0, size) in readFile. A 159-byte crafted file panics OpenFile and OpenReader with runtime error: …
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 …
ColumnNameToNumber (lib.go:220-237) accumulates the bijective base-26 value of a column name in an int64 with only an upper-bound check (col > MaxColumns) applied after the loop and no overflow detection. A 14-letter column name whose true value is 3·2⁶⁴ — e.g. VGWQHXLSDVIKWV — wraps to col = 0 and passes the guard, so CellNameToCoordinates("VGWQHXLSDVIKWV1") returns (col=0, row=1, err=nil). When normalizing a r="0" row, checkSheetR0 (excelize.go:417-443, called from checkSheet at excelize.go:405) …
ColumnNameToNumber (lib.go:220-237) accumulates the bijective base-26 value of a column name in an int64 with only an upper-bound check (col > MaxColumns) applied after the loop and no overflow detection. A 14-letter column name whose true value is 3·2⁶⁴ — e.g. VGWQHXLSDVIKWV — wraps to col = 0 and passes the guard, so CellNameToCoordinates("VGWQHXLSDVIKWV1") returns (col=0, row=1, err=nil). When normalizing a r="0" row, checkSheetR0 (excelize.go:417-443, called from checkSheet at excelize.go:405) …
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 …
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 …
Excelize parses shared-string cell values with strconv.Atoi and checks only the upper bound before indexing the shared string slice. If an XLSX file contains a shared-string cell with <v>-1</v>, the parsed index is negative. The upper-bound check still passes (len(sharedStrings) > -1), and Excelize indexes sharedStrings[-1], causing a runtime panic. This was reproduced on the current default branch commit 1213a8bd7c5ab360554603ac5c995ccaf6eb4314 and the latest release tag v2.10.1 (5ad5ab3af0054c55bdce09f1530085600e9f2e45). The issue is …
Excelize parses shared-string cell values with strconv.Atoi and checks only the upper bound before indexing the shared string slice. If an XLSX file contains a shared-string cell with <v>-1</v>, the parsed index is negative. The upper-bound check still passes (len(sharedStrings) > -1), and Excelize indexes sharedStrings[-1], causing a runtime panic. This was reproduced on the current default branch commit 1213a8bd7c5ab360554603ac5c995ccaf6eb4314 and the latest release tag v2.10.1 (5ad5ab3af0054c55bdce09f1530085600e9f2e45). The issue is …
The checkSheet() function in github.com/xuri/excelize/v2 uses an attacker-controlled <row r="N"> XML attribute value directly as the length argument to make([]xlsxRow, row) without validating it against the Excel row limit (TotalRows = 1,048,576). A specially crafted XLSX file can trigger two denial-of-service variants: (A) an out-of-memory process kill when r=2147483647 forces a ~16 GB allocation attempt, and (B) a runtime panic via out-of-bounds slice indexing when r=-1. Any service that opens …