Every CVE Search/ Get API key

CVE-2026-107224

Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader

6.5 MediumFix available

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.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
Required

What an attacker can affect

Confidentiality
None
Integrity
None
Availability
High

Exploit likelihood

0.31%Down 0.1 points since Oct 8, 2026
90%50%10%1%

What each source reports

CVE List Score used 6.5

Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader

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.

Record
CVE-2026-107224
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
Weakness
CWE-190, CWE-681
Updated
2026-11-02
EUVDEUVD-2026-94555 6.5

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.

Record
EUVD-2026-94555
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
Updated
2026-11-02
GitHub AdvisoriesGHSA-fw94-4wwp-w8pw 6.5

Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader

Summary

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: makeslice: cap out of range.

The guard, at lib.go:44-46 on HEAD ae2113b:

fileSize := v.FileInfo().Size()
unzipSize += fileSize
if unzipSize > f.options.UnzipSizeLimit {
	return fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit)
}

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.

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

// lib.go:150
dat := make([]byte, 0, file.FileInfo().Size())

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.

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

Reproduction, executed against HEAD ae2113b (go1.26.5)

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

entry "xl/worksheets/sheet1.xml" FileInfo().Size() = -9223372036854775808
excelize.OpenReader(bytes.NewReader(raw))  ->  panic: runtime error: makeslice: cap out of range

Control, the identical archive with UncompressedSize64 = 0x7FFFFFFFFFFFFFFF:

entry "xl/worksheets/sheet1.xml" FileInfo().Size() = 9223372036854775807
excelize.OpenReader(bytes.NewReader(raw))  ->  err = "unzip size exceeds the 16777216000 bytes limit"

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

What I verified and what I did not

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

Attacker model

Any service that opens a spreadsheet it did not produce. The payload is 159 bytes, needs no authentication, and is deterministic.

Suggested fix

Compare against the limit in unsigned space, and bound the allocation independently rather than trusting the header:

if v.UncompressedSize64 > uint64(f.options.UnzipSizeLimit) {
…
Record
GHSA-fw94-4wwp-w8pw
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
Weakness
CWE-190, CWE-681
Updated
2026-11-02
NVD 6.5

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.

Record
CVE-2026-107224
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
Weakness
CWE-190, CWE-681
Updated
2026-11-02
OSV 6.5

Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader

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.

Record
CVE-2026-107224
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
Weakness
CWE-190, CWE-681
Updated
2026-11-06
OSVGHSA-fw94-4wwp-w8pw 6.5

Excelize: A Zip64 uncompressed-size of 2^63 panics OpenFile/OpenReader

Summary

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: makeslice: cap out of range.

The guard, at lib.go:44-46 on HEAD ae2113b:

fileSize := v.FileInfo().Size()
unzipSize += fileSize
if unzipSize > f.options.UnzipSizeLimit {
	return fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit)
}

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.

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

// lib.go:150
dat := make([]byte, 0, file.FileInfo().Size())

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.

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

Reproduction, executed against HEAD ae2113b (go1.26.5)

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

entry "xl/worksheets/sheet1.xml" FileInfo().Size() = -9223372036854775808
excelize.OpenReader(bytes.NewReader(raw))  ->  panic: runtime error: makeslice: cap out of range

Control, the identical archive with UncompressedSize64 = 0x7FFFFFFFFFFFFFFF:

entry "xl/worksheets/sheet1.xml" FileInfo().Size() = 9223372036854775807
excelize.OpenReader(bytes.NewReader(raw))  ->  err = "unzip size exceeds the 16777216000 bytes limit"

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

What I verified and what I did not

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

Attacker model

Any service that opens a spreadsheet it did not produce. The payload is 159 bytes, needs no authentication, and is deterministic.

Suggested fix

Compare against the limit in unsigned space, and bound the allocation independently rather than trusting the header:

if v.UncompressedSize64 > uint64(f.options.UnzipSizeLimit) {
…
Record
GHSA-fw94-4wwp-w8pw
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
Weakness
CWE-190, CWE-681
Updated
2026-11-06

Affected products

Affected packages

Affected packages
PackageAffectedFixed
github.com/xuri/excelize/v2
Go
2.1.0 up to 2.11.1-0.20260805032953-db93f8d89de7
2.11.1-0.20260805032953-db93f8d89de7

History

History
RecordedChangedSources
aliases, factsOSV
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-107224 is a medium vulnerability (CVSS 6.5) in Excelize, published October 7, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.31%, the 23rd percentile. Fixed in github.com/xuri/excelize/v2 2.11.1-0.20260805032953-db93f8d89de7.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open