Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pyiceberg/table/deletion_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ def _deserialize_bitmap(pl: bytes) -> list[BitMap]:
number_of_bitmaps = int.from_bytes(pl[0:8], byteorder="little")
pl = pl[8:]

# Every bitmap contributes at least a 4-byte key, so a count that cannot fit
# in the remaining payload is invalid and must not be used as a loop bound.
if number_of_bitmaps * 4 > len(pl):
raise ValueError(f"Payload declares {number_of_bitmaps} bitmaps, but only holds {len(pl)} bytes")

bitmaps = []
last_key = -1
for _ in range(number_of_bitmaps):
Expand Down
9 changes: 9 additions & 0 deletions tests/table/test_deletion_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ def test_map_spread_vals() -> None:
assert expected == actual


def test_map_declared_count_exceeds_payload() -> None:
# A truncated payload that claims a large number of bitmaps must be rejected,
# rather than driving the deserialization loop on data that is not there.
puffin = (2**32).to_bytes(8, byteorder="little") + b"\x00\x00\x00\x00"

with pytest.raises(ValueError, match="Payload declares 4294967296 bitmaps, but only holds 4 bytes"):
_ = DeletionVector._deserialize_bitmap(puffin)


def test_map_high_vals() -> None:
puffin = _open_file("64maphighvals.bin")

Expand Down
Loading