qax-os/excelize · Security
Excelize: extractPart allocates attacker-controlled, unbounded and negative-sized buffers from CFB directory entries: remote panic / OOM DoS
- GHSA ID
- GHSA-x2q3-8cjh-766f
- CVE ID
- CVE-2026-107215
- Severity
- high
- Published
- Updated
- CVSS
- 7.5 · CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
- CVSS v3
- 7.5 · CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Description
Summary
extractPart allocates directly from the mscfb directory-entry size with no clamping (crypt.go:198, same missing bound at crypt.go:204):
buf := make([]byte, entry.Size)
entry.Size is the raw streamSize field from the attacker-controlled CFB directory entry (mscfb v1.0.8 fixFile, file.go:109-120: full uint64 → int64 for major version 4, low uint32 for v3). mscfb only detects short sector chains later, inside File.Read (file.go:485-489 "emergency brake") — i.e. after the allocation. The zip path already enforces UnzipSizeLimit, and the KDF path bounds work with maxSpinCount; this path consults no limit at all.
Details
Two consequences, both execution-confirmed against pristine master (ecd99d761fe0, 2026-09-08):
- Hard crash: a version-4 CFB declaring
streamSize = 0xFFFFFFFFFFFFFFFFyieldsentry.Size == -1→panic: runtime error: makeslice: len out of range, which escapesDecrypt(openReaderAtconverts errors, not panics,excelize.go:210-214) and kills the process. - Memory exhaustion: any CFB (v3 suffices) declaring
streamSizeup to0xFFFFFFFFforces up to 4 GiB of zeroed allocation from a ~12 KB file — an amplification factor of ~350,000×, trivially repeated per request in memory-limited deployments.
Call path: OpenFile/OpenReader/OpenBytes → openReaderAt (excelize.go:208-215, OLE magic sniff) → Decrypt (crypt.go:145-150) → extractPart (crypt.go:198).
PoC
A standalone program (public API only) was provided to the maintainer by email (2-extractpart-oom): it builds a valid-enough v4 (4096-byte sector) CFB — header (signature, sector shift 0x0C, DIFAT[0]=1), one directory sector with Root Entry (typeID 5, child=1) and a stream entry EncryptionInfo (objectType=2, startingSector=endOfChain, streamSize=0xFFFFFFFFFFFFFFFF). mscfb.New succeeds, doc.Next() returns the entry, and extractPart calls make([]byte, -1) before any chain validation. On master ecd99d761fe0 the program prints panic: runtime error: makeslice: len out of range; with the proposed patch it prints BLOCKED. An -oom flag demonstrates the 4 GiB allocation variant with streamSize = 0xFFFFFFFF.
Impact
An attacker uploads any file starting with the OLE signature D0 CF 11 E0 A1 B1 1A E1 (~12 KB is enough) to a service that calls excelize.OpenFile/OpenReader/OpenBytes on it (or calls Decrypt directly): remote, unauthenticated process crash (hard panic) or forced 4 GiB allocation per request (OOM) with a ~350,000× amplification factor.
Proposed fix
Bound the declared stream size before allocating in extractPart: reject negative sizes and sizes above a sane policy cap (1 GiB is far above any real encrypted-workbook part) with ErrWorkbookFileFormat, so malformed CFBs are rejected as errors instead of panicking or OOMing. A complete patch has been provided to the maintainer; verified that the patch blocks the PoC while legitimate Encrypt() output still opens.
CWEs
- CWE-789 · Memory Allocation with Excessive Size Value
References
- https://github.com/qax-os/excelize/security/advisories/GHSA-x2q3-8cjh-766f ↗
- https://nvd.nist.gov/vuln/detail/CVE-2026-107215 ↗
- https://github.com/qax-os/excelize/pull/2396 ↗
- https://github.com/qax-os/excelize/commit/5f636f9dcde55911f14290060b017f0cc88cd694 ↗
- https://github.com/advisories/GHSA-x2q3-8cjh-766f ↗