Skip to content

Commit 1c61208

Browse files
fix: add _MAX_PARAM_COUNT cap to decode_params_tuple
Defense-in-depth: reject V1 params tuples claiming more than 100,000 parameters early, before allocating type-code lists. Matches the pattern used by _MAX_COLUMN_COUNT, _MAX_FILE_COUNT, and _MAX_NODE_COUNT in responses.py. SQLite's default limit is 999 parameters (compile-time max 32766). Closes #170. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6c3458c commit 1c61208

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

src/dqlitewire/tuples.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
# Valid ValueType codes as integers, for fast membership testing in hot paths.
1818
_VALID_TYPE_CODES = frozenset(int(v) for v in ValueType)
1919

20+
# Defense-in-depth cap on parameter count, matching the pattern used by
21+
# _MAX_COLUMN_COUNT, _MAX_FILE_COUNT, and _MAX_NODE_COUNT in responses.py.
22+
# SQLite's default limit is 999 (compile-time max 32766).
23+
_MAX_PARAM_COUNT = 100_000
24+
2025

2126
class RowMarker(Enum):
2227
"""Row marker detected during header parsing."""
@@ -134,6 +139,11 @@ def decode_params_tuple(
134139
# Count was externally provided, no data consumed
135140
return [], 0
136141

142+
# Defense-in-depth: reject unreasonable parameter counts early,
143+
# before allocating type-code lists or iterating over values.
144+
if count > _MAX_PARAM_COUNT:
145+
raise DecodeError(f"Parameter count {count} exceeds maximum ({_MAX_PARAM_COUNT})")
146+
137147
# Header: count field (if read from data) + type codes, padded to word boundary.
138148
# When count was externally provided, the data starts directly with type codes
139149
# so count_size is 0.

tests/test_tuples.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
"""Tests for tuple encoding/decoding."""
22

3+
import struct
4+
5+
import pytest
6+
37
from dqlitewire.constants import ValueType
8+
from dqlitewire.exceptions import DecodeError
49
from dqlitewire.tuples import (
510
RowMarker,
611
decode_params_tuple,
@@ -565,3 +570,24 @@ def test_roundtrip_with_null(self) -> None:
565570
encoded = encode_row_values(values, types)
566571
decoded, _ = decode_row_values(encoded, types)
567572
assert decoded == values
573+
574+
575+
class TestParamsTupleMaxCount:
576+
"""170: decode_params_tuple should reject excessive parameter counts."""
577+
578+
def test_v1_count_exceeding_cap_raises(self) -> None:
579+
"""A V1 params tuple claiming 200_000 params should be rejected."""
580+
# Craft a V1 header with count = 200_000
581+
count = 200_000
582+
header = struct.pack("<I", count)
583+
# Pad to a full 8-byte word
584+
data = header + b"\x00" * 4
585+
with pytest.raises(DecodeError, match="exceeds maximum"):
586+
decode_params_tuple(data, schema=1)
587+
588+
def test_v0_count_within_cap_succeeds(self) -> None:
589+
"""V0 counts (max 255) should never hit the cap."""
590+
# Encode a valid empty-params V0 tuple (count=0)
591+
data = b"\x00" + b"\x00" * 7 # count=0, padded to 8 bytes
592+
result, _ = decode_params_tuple(data, schema=0)
593+
assert result == []

0 commit comments

Comments
 (0)