{"schema":"threatline.record/2","record":{"id":"CVE-2026-107224","aliases":["GHSA-fw94-4wwp-w8pw"],"summary":"Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader","details":"### Summary\n\nA Zip64 uncompressed-size of 2^63 casts to a negative int64 that bypasses the unzip-size guard and reaches make([]byte, 0, negativeCap)\n\nA 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: makeslice: cap out of range`.\n\nThe guard, at `lib.go:44-46` on HEAD `ae2113b`:\n\n```go\nfileSize := v.FileInfo().Size()\nunzipSize += fileSize\nif unzipSize > f.options.UnzipSizeLimit {\n\treturn fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit)\n}\n```\n\n`FileInfo().Size()` returns `int64(UncompressedSize64)`. `UncompressedSize64` is read from the Zip64 extended-information extra record in the central directory and is fully attacker controlled, so any value with the top bit set arrives as a negative `int64`. `unzipSize` then goes negative and the comparison against `UnzipSizeLimit` (default `1000 << 24`, `templates.go:193`) is false no matter how many such entries the archive contains.\n\nThe same negative value also fails the two stream-to-temp checks at `lib.go:53` and `lib.go:64` (`fileSize > f.options.UnzipXMLSizeLimit`), so the entry is not diverted to a temp file and control reaches `readFile`:\n\n```go\n// lib.go:150\ndat := make([]byte, 0, file.FileInfo().Size())\n```\n\n`make` with a negative capacity panics. There is no `recover()` in any non-test file in the library, so the panic leaves the public API and takes down the calling goroutine.\n\nThe asymmetry inside this same function is the clearest way to see it: `unzipToTemp` (`lib.go:83`) handles an entry with a plain `io.Copy` and never pre-allocates from the declared size, so it is indifferent to whatever the header claims. Only the pre-allocating branch trusts that number, and it trusts it after a signed comparison that a wrapped value walks straight through.\n\n## Reproduction, executed against HEAD `ae2113b` (go1.26.5)\n\nA 159-byte archive was built with one stored entry named `xl/worksheets/sheet1.xml`: truthful 1-byte local header, central-directory 32-bit uncompressed size set to the `0xFFFFFFFF` Zip64 sentinel, and a Zip64 extra record (tag `0x0001`, data size 8) declaring `UncompressedSize64 = 0x8000000000000000`.\n\n```\nentry \"xl/worksheets/sheet1.xml\" FileInfo().Size() = -9223372036854775808\nexcelize.OpenReader(bytes.NewReader(raw))  ->  panic: runtime error: makeslice: cap out of range\n```\n\nControl, the identical archive with `UncompressedSize64 = 0x7FFFFFFFFFFFFFFF`:\n\n```\nentry \"xl/worksheets/sheet1.xml\" FileInfo().Size() = 9223372036854775807\nexcelize.OpenReader(bytes.NewReader(raw))  ->  err = \"unzip size exceeds the 16777216000 bytes limit\"\n```\n\nThe control isolates the defect to the sign flip rather than to size magnitude: the guard behaves correctly for any positive declared size and fails only once the value wraps. `archive/zip` itself accepts the archive without complaint, so nothing upstream of excelize rejects it.\n\n## What I verified and what I did not\n\nI ran both cases above myself and confirmed each cited line at HEAD. I caught the panic with a deliberate `recover` in my harness so I could print it; nothing in excelize recovers it, which I checked by grepping every non-test `.go` file. I did not separately run the `OpenFile` variant, since `OpenFile` reaches the same `ReadZipReader` loop, and I did not attempt to turn the bypassed size cap into a separate decompression-bomb primitive.\n\n## Attacker model\n\nAny service that opens a spreadsheet it did not produce. The payload is 159 bytes, needs no authentication, and is deterministic.\n\n## Suggested fix\n\nCompare against the limit in unsigned space, and bound the allocation independently rather than trusting the header:\n\n```go\nif v.UncompressedSize64 > uint64(f.options.UnzipSizeLimit) {\n\treturn fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit)\n}\n```\n\nand in `readFile`, reject or clamp a declared size that is negative or larger than the limit before calling `make`. The same unsigned treatment applies to the two `UnzipXMLSizeLimit` comparisons, which currently route a wrapped size away from the safe streaming path and into the pre-allocating one.","categories":["Libraries & packages"],"product":"github.com/xuri/excelize/v2","vendor":"qax-os","ecosystem":"go","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-fw94-4wwp-w8pw","source_modified_at":"2026-10-07T20:22:47Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"go","name":"github.com/xuri/excelize/v2"},"range":">= 2.1.0, < 2.11.1-0.20260805032953-db93f8d89de7"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-107224","source_modified_at":"2026-10-07T19:30:13.733Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"excelize","vendor":"qax-os","versions":[{"version":">= 2.1.0, <= 2.11.0","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-fw94-4wwp-w8pw","source_modified_at":"2026-10-07T20:30:07.330722837Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Go","name":"github.com/xuri/excelize/v2","purl":"pkg:golang/github.com/xuri/excelize/v2"},"ranges":[{"events":[{"introduced":"2.1.0"},{"fixed":"2.11.1-0.20260805032953-db93f8d89de7"}],"type":"SEMVER"}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-fw94-4wwp-w8pw","source_modified_at":"2026-10-07T20:22:47Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"go","name":"github.com/xuri/excelize/v2"},"version":"2.11.1-0.20260805032953-db93f8d89de7"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-fw94-4wwp-w8pw","source_modified_at":"2026-10-07T20:30:07.330722837Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Go","name":"github.com/xuri/excelize/v2","purl":"pkg:golang/github.com/xuri/excelize/v2"},"version":"2.11.1-0.20260805032953-db93f8d89de7"}],"cvss":6.5,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.00306,"percentile":0.22267,"score_date":"2026-10-23"},"published_at":"2026-10-07T18:53:27.282Z","modified_at":"2026-10-07T20:30:07.330722837Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-fw94-4wwp-w8pw","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-07T20:22:47Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-107224","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-07T19:30:13.733Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-fw94-4wwp-w8pw","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-07T20:30:07.330722837Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-107224","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/qax-os/excelize/security/advisories/GHSA-fw94-4wwp-w8pw","tags":["exploit"]},{"source":"CVE List","url":"https://github.com/qax-os/excelize/pull/2369","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/qax-os/excelize/commit/db93f8d89de71589069a54d1b998af02e3c5f7cd","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-fw94-4wwp-w8pw","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"no","technical_impact":"partial","assessed_at":"2026-10-07T19:29:38.692124Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-07T20:30:07.330722837Z","content_hash":"8abed21de196d932dfb5251fee0fdd9313562e4a693743978bf23afd1de2e38a"}}
