GHSA-5H23-36RV-PM65

Vulnerability from github – Published: 2026-10-07 20:22 – Updated: 2026-10-07 20:22
VLAI
Summary
Excelize: GetStyle panics on a negative fillId, borderId or fontId in styles.xml
Details

Summary

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 different file. That one was fixed by adding the missing lower bound; these three sites still lack it.

Where it is

styles.go, in GetStyle, lines 1683, 1686 and 1689:

xf := s.CellXfs.Xf[idx]
if extractStyleCondFuncs["fill"](xf, s) {
    f.extractFills(s.Fills.Fill[*xf.FillID], s, style)
}
if extractStyleCondFuncs["border"](xf, s) {
    f.extractBorders(s.Borders.Border[*xf.BorderID], s, style)
}
if extractStyleCondFuncs["font"](xf, s) {
    style.Font = extractFont(s.Fonts.Font[*xf.FontID])
}

The conditions, at lines 1171 to 1185, bound only the top:

"fill": func(xf xlsxXf, s *xlsxStyleSheet) bool {
    return (xf.ApplyFill == nil || (xf.ApplyFill != nil && *xf.ApplyFill)) &&
        xf.FillID != nil && s.Fills != nil &&
        *xf.FillID < len(s.Fills.Fill)
},

*xf.FillID < 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.

The correct pattern is already in the same function, six lines above at 1677:

if idx < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= idx {
    return style, newInvalidStyleID(idx)
}

and again at 1783. So the style index itself is guarded on both sides while the three ids it leads to are not.

Proof of concept

Executed 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:

control (all >= 0)           ok style=true err=<nil>
fillId=-1                    PANIC: runtime error: index out of range [-1]
borderId=-1                  PANIC: runtime error: index out of range [-1]
fontId=-1                    PANIC: runtime error: index out of range [-1]

The 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.

Impact

Any 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.

No memory corruption and no information disclosure. Availability only.

Suggested fix

Add the lower bound to each of the three conditions, matching what line 1677 already does for the style index:

*xf.FillID >= 0 && *xf.FillID < len(s.Fills.Fill)

and the same for BorderID and FontID. Three one-line changes in extractStyleCondFuncs.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/xuri/excelize/v2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.8.0"
            },
            {
              "fixed": "2.11.1-0.20260731010303-ae2113b410e5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-107225"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-129",
      "CWE-20"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-07T20:22:39Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "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 \u0026\u0026 *xf.ApplyFill)) \u0026\u0026\n        xf.FillID != nil \u0026\u0026 s.Fills != nil \u0026\u0026\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 \u0026\u0026 *xf.FillID \u003c len(s.Fills.Fill)\n```\n\nand the same for `BorderID` and `FontID`. Three one-line changes in `extractStyleCondFuncs`.",
  "id": "GHSA-5h23-36rv-pm65",
  "modified": "2026-10-07T20:22:39Z",
  "published": "2026-10-07T20:22:39Z",
  "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"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Excelize: GetStyle panics on a negative fillId, borderId or fontId in styles.xml"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

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.

Loading…

Loading…

Loading…

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.


Loading…