{"id":"CVE-2026-107224","aliases":["EUVD-2026-94555","GHSA-fw94-4wwp-w8pw","GO-2026-6679"],"status":"published","title":"Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.1.0 to 2.11.0, a Zip64 uncompressed size with the high bit set is converted from uint64 to a negative int64 before signed size-limit checks and allocation. ReadZipReader obtains UncompressedSize64 through FileInfo.Size and passes the wrapped negative value to readFile. When a crafted Zip64 entry declares an uncompressed size from 2^63 through 2^64-1 and the workbook is opened, the negative size bypasses unzip limits and reaches make as a negative capacity, allowing an attacker to panic during workbook opening. No fixed version is available as of this review.","severity":{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00306,"percentile":0.23754,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-190","CWE-681"],"published":"2026-10-07T18:53:27Z","modified":"2026-11-05T23:25:35Z","fixes":[{"ecosystem":"Go","package":"github.com/xuri/excelize/v2","fixed_in":["2.11.1-0.20260805032953-db93f8d89de7"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Go","package":"github.com/xuri/excelize/v2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.1.0"},{"fixed":"2.11.1-0.20260805032953-db93f8d89de7"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/xuri/excelize/v2","ranges":[{"type":"SEMVER","events":[{"introduced":"2.1.0"},{"fixed":"2.11.1-0.20260805032953-db93f8d89de7"}]}]}],"products":[{"vendor":"excelize","product":"excelize"},{"vendor":"qax-os","product":"excelize"}],"references":[{"url":"https://github.com/qax-os/excelize/security/advisories/GHSA-fw94-4wwp-w8pw","tags":["x_refsource_CONFIRM","exploit","Exploit","Vendor Advisory","advisory","web"]},{"url":"https://github.com/qax-os/excelize/pull/2369","tags":["x_refsource_MISC","Issue Tracking","Patch","fix","web"]},{"url":"https://github.com/qax-os/excelize/commit/db93f8d89de71589069a54d1b998af02e3c5f7cd","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/advisories/GHSA-fw94-4wwp-w8pw"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/107xxx/CVE-2026-107224.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107224","tags":["advisory"]},{"url":"https://github.com/qax-os/excelize","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-107224","title":"Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.1.0 to 2.11.0, a Zip64 uncompressed size with the high bit set is converted from uint64 to a negative int64 before signed size-limit checks and allocation. ReadZipReader obtains UncompressedSize64 through FileInfo.Size and passes the wrapped negative value to readFile. When a crafted Zip64 entry declares an uncompressed size from 2^63 through 2^64-1 and the workbook is opened, the negative size bypasses unzip limits and reaches make as a negative capacity, allowing an attacker to panic during workbook opening. No fixed version is available as of this review.","status":"published","published":"2026-10-07T18:53:27.282Z","modified":"2026-10-07T19:30:13.733Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","level":"medium","assessor":"cna"}],"cwes":["CWE-190","CWE-681"],"updated_at":"2026-11-02T13:45:14.072357Z"}],"euvd":[{"id":"EUVD-2026-94555","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.1.0 to 2.11.0, a Zip64 uncompressed size with the high bit set is converted from uint64 to a negative int64 before signed size-limit checks and allocation. ReadZipReader obtains UncompressedSize64 through FileInfo.Size and passes the wrapped negative value to readFile. When a crafted Zip64 entry declares an uncompressed size from 2^63 through 2^64-1 and the workbook is opened, the negative size bypasses unzip limits and reaches make as a negative capacity, allowing an attacker to panic during workbook opening. No fixed version is available as of this review.","status":"published","published":"2026-10-07T18:53:27Z","modified":"2026-10-07T19:30:13Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","level":"medium"}],"updated_at":"2026-11-02T17:27:33.904651Z"}],"ghsa":[{"id":"GHSA-fw94-4wwp-w8pw","title":"Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader","description":"### 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 \u003e 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 \u003c\u003c 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 \u003e 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))  -\u003e  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))  -\u003e  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 \u003e 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.","status":"published","published":"2026-10-07T20:22:45Z","modified":"2026-10-07T20:22:47Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-190","CWE-681"],"updated_at":"2026-11-02T14:41:28.013673Z"}],"nvd":[{"id":"CVE-2026-107224","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.1.0 to 2.11.0, a Zip64 uncompressed size with the high bit set is converted from uint64 to a negative int64 before signed size-limit checks and allocation. ReadZipReader obtains UncompressedSize64 through FileInfo.Size and passes the wrapped negative value to readFile. When a crafted Zip64 entry declares an uncompressed size from 2^63 through 2^64-1 and the workbook is opened, the negative size bypasses unzip limits and reaches make as a negative capacity, allowing an attacker to panic during workbook opening. No fixed version is available as of this review.","status":"published","published":"2026-10-07T19:17:35.14Z","modified":"2026-10-13T14:17:32.22Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","level":"medium","assessor":"secondary"}],"cwes":["CWE-190","CWE-681"],"updated_at":"2026-11-02T14:12:54.809359Z"}],"osv":[{"id":"CVE-2026-107224","title":"Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader","description":"Excelize is a Go language library for reading and writing Microsoft Excel spreadsheets. From 2.1.0 to 2.11.0, a Zip64 uncompressed size with the high bit set is converted from uint64 to a negative int64 before signed size-limit checks and allocation. ReadZipReader obtains UncompressedSize64 through FileInfo.Size and passes the wrapped negative value to readFile. When a crafted Zip64 entry declares an uncompressed size from 2^63 through 2^64-1 and the workbook is opened, the negative size bypasses unzip limits and reaches make as a negative capacity, allowing an attacker to panic during workbook opening. No fixed version is available as of this review.","status":"published","published":"2026-10-07T18:53:27.282Z","modified":"2026-11-05T23:25:35.445581236Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-190","CWE-681"],"updated_at":"2026-11-06T00:02:13.631839Z"},{"id":"GHSA-fw94-4wwp-w8pw","title":"Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader","description":"### 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 \u003e 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 \u003c\u003c 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 \u003e 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))  -\u003e  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))  -\u003e  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 \u003e 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.","status":"published","published":"2026-10-07T20:22:45Z","modified":"2026-11-05T23:25:35.445581236Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-190","CWE-681"],"updated_at":"2026-11-06T00:02:13.631839Z"}]}}
