{"id":"GHSA-5h23-36rv-pm65","summary":"Excelize: GetStyle panics on a negative fillId, borderId or fontId in styles.xml","details":"## Summary\n\n`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.\n\nSame defect class as GHSA-48hm-4h8j-58fg, the negative shared-string index, in a different file. That one was fixed by adding the missing lower bound; these three sites still lack it.\n\n## Where it is\n\n`styles.go`, in `GetStyle`, lines 1683, 1686 and 1689:\n\n```go\nxf := s.CellXfs.Xf[idx]\nif extractStyleCondFuncs[\"fill\"](xf, s) {\n    f.extractFills(s.Fills.Fill[*xf.FillID], s, style)\n}\nif extractStyleCondFuncs[\"border\"](xf, s) {\n    f.extractBorders(s.Borders.Border[*xf.BorderID], s, style)\n}\nif extractStyleCondFuncs[\"font\"](xf, s) {\n    style.Font = extractFont(s.Fonts.Font[*xf.FontID])\n}\n```\n\nThe conditions, at lines 1171 to 1185, bound only the top:\n\n```go\n\"fill\": func(xf xlsxXf, s *xlsxStyleSheet) bool {\n    return (xf.ApplyFill == nil || (xf.ApplyFill != nil && *xf.ApplyFill)) &&\n        xf.FillID != nil && s.Fills != nil &&\n        *xf.FillID \u003c len(s.Fills.Fill)\n},\n```\n\n`*xf.FillID \u003c len(...)` is satisfied by any negative value. `FillID`, `BorderID` and `FontID` are `*int` unmarshalled directly from the `fillId`, `borderId` and `fontId` attributes, so the value is whatever the file says. The border and font conditions have the same shape.\n\nThe correct pattern is already in the same function, six lines above at 1677:\n\n```go\nif idx \u003c 0 || s.CellXfs == nil || len(s.CellXfs.Xf) \u003c= idx {\n    return style, newInvalidStyleID(idx)\n}\n```\n\nand again at 1783. So the style index itself is guarded on both sides while the three ids it leads to are not.\n\n## Proof of concept\n\nExecuted against `master` at `f98df08`, which is the merge of #2366, so this is current rather than historical. The harness builds a normal styled workbook with excelize, rewrites one attribute of the `cellXfs` entry inside the zip to `-1`, reopens it and calls `GetCellStyle` then `GetStyle`:\n\n```\ncontrol (all \u003e= 0)           ok style=true err=\u003cnil\u003e\nfillId=-1                    PANIC: runtime error: index out of range [-1]\nborderId=-1                  PANIC: runtime error: index out of range [-1]\nfontId=-1                    PANIC: runtime error: index out of range [-1]\n```\n\nThe control confirms the harness reads a valid file correctly, so the three panics are the negative ids rather than a broken fixture. Worth mentioning because my first attempt at this harness patched only `fillId` and appeared to show the other two were fine; they are not, the replacement had simply missed them.\n\n## Impact\n\nAny application that opens an untrusted `.xlsx` and reads cell styling crashes. `GetStyle` is reached through `GetCellStyle` and from the style-copying and rendering paths, so it sits on the ordinary read path. With no `recover()` anywhere in excelize, a CLI or worker exits and a service returns 500 per request unless the caller installed its own recovery middleware.\n\nNo memory corruption and no information disclosure. Availability only.\n\n## Suggested fix\n\nAdd the lower bound to each of the three conditions, matching what line 1677 already does for the style index:\n\n```go\n*xf.FillID \u003e= 0 && *xf.FillID \u003c len(s.Fills.Fill)\n```\n\nand the same for `BorderID` and `FontID`. Three one-line changes in `extractStyleCondFuncs`.","aliases":["CVE-2026-107225"],"modified":"2026-10-07T20:30:06.927051800Z","published":"2026-10-07T20:22:39Z","database_specific":{"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-10-07T20:22:39Z","nvd_published_at":null,"cwe_ids":["CWE-129","CWE-20"]},"references":[{"type":"WEB","url":"https://github.com/qax-os/excelize/security/advisories/GHSA-5h23-36rv-pm65"},{"type":"WEB","url":"https://github.com/qax-os/excelize/pull/2367"},{"type":"WEB","url":"https://github.com/qax-os/excelize/commit/ae2113b410e51f6a141c396a59eda8c42b91bc22"},{"type":"PACKAGE","url":"https://github.com/qax-os/excelize"}],"affected":[{"package":{"name":"github.com/xuri/excelize/v2","ecosystem":"Go","purl":"pkg:golang/github.com/xuri/excelize/v2"},"ranges":[{"type":"SEMVER","events":[{"introduced":"2.8.0"},{"fixed":"2.11.1-0.20260731010303-ae2113b410e5"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-5h23-36rv-pm65/GHSA-5h23-36rv-pm65.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"}]}