Repository navigation
Limit zip file entries to 100MB by default #406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8894756
b6123f1
5756633
ba37fcb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| from typing import IO, Union | ||
| from zipfile import BadZipFile, ZipFile, ZipInfo | ||
|
|
||
| DEFAULT_MAX_UNCOMPRESSED_SIZE = 100 * 1024 * 1024 # 100 MB | ||
| DEFAULT_MAX_COMPRESSION_RATIO = 100.0 # 100:1 ratio limit | ||
|
|
||
|
|
||
| def open_bounded_stream( | ||
| archive: ZipFile, | ||
| entry: Union[str, ZipInfo], | ||
| max_uncompressed_size: int = DEFAULT_MAX_UNCOMPRESSED_SIZE, | ||
| max_compression_ratio: float = DEFAULT_MAX_COMPRESSION_RATIO, | ||
| ) -> IO[bytes]: | ||
| """Opens a zip entry for reading, refusing entries that are too large or too highly compressed. | ||
|
|
||
| Raises `BadZipFile` if the entry's declared size or compression ratio exceeds the limits. `ZipFile` never yields | ||
| more than the declared size, so an entry whose header understates its size fails the CRC check with `BadZipFile`. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI: F4. The new docstring promises more than CPython delivers.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. F4 still applies at bdce2ab: the docstring now says outright that an understated header "fails the CRC check with |
||
| """ | ||
| if isinstance(entry, str): | ||
| entry = archive.getinfo(entry) | ||
|
|
||
| if entry.file_size > max_uncompressed_size: | ||
| raise BadZipFile("Entry uncompressed size exceeds maximum allowed limit.") | ||
|
|
||
| if entry.compress_size > 0 and entry.file_size / entry.compress_size > max_compression_ratio: | ||
|
claude[bot] marked this conversation as resolved.
Resolved
|
||
| raise BadZipFile("Compression ratio exceeds safe threshold.") | ||
|
|
||
| return archive.open(entry, "r") | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: F5. With
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI: F6. A bzip2 or LZMA entry is not bounded by these checks, even if the reads are chunked. For those types, |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| from io import BytesIO | ||
| from zipfile import ZIP_DEFLATED, BadZipFile, ZipFile | ||
|
|
||
| from pytest import raises | ||
|
|
||
| from machine.utils.zip_entry_utils import open_bounded_stream | ||
|
|
||
|
|
||
| def create_zip(name: str, content: bytes) -> ZipFile: | ||
| buffer = BytesIO() | ||
| with ZipFile(buffer, "w", ZIP_DEFLATED) as archive: | ||
| archive.writestr(name, content) | ||
| return ZipFile(buffer, "r") | ||
|
|
||
|
|
||
| def test_open_bounded_stream_returns_readable_stream() -> None: | ||
| with create_zip("test.txt", b"Hello World") as archive: | ||
| with open_bounded_stream(archive, "test.txt", max_uncompressed_size=100) as stream: | ||
| assert stream.read() == b"Hello World" | ||
|
|
||
|
|
||
| def test_open_bounded_stream_empty_entry() -> None: | ||
| with create_zip("empty.txt", b"") as archive: | ||
| with open_bounded_stream(archive, "empty.txt") as stream: | ||
| assert stream.read() == b"" | ||
|
|
||
|
|
||
| def test_open_bounded_stream_header_size_exceeded() -> None: | ||
| with create_zip("large.txt", bytes(200)) as archive: | ||
| with raises(BadZipFile): | ||
| open_bounded_stream(archive, "large.txt", max_uncompressed_size=100) | ||
|
|
||
|
|
||
| def test_open_bounded_stream_compression_ratio_exceeded() -> None: | ||
| with create_zip("bomb.txt", bytes(10_000)) as archive: | ||
| with raises(BadZipFile): | ||
| open_bounded_stream(archive, "bomb.txt", max_uncompressed_size=20_000, max_compression_ratio=2.0) |
Uh oh!
There was an error while loading. Please reload this page.