Skip to content

Reject FixedString loads whose byte size overflows - #579

Open
iliaal wants to merge 1 commit into
ClickHouse:masterfrom
iliaal:fix/fixedstring-size-overflow
Open

iliaal wants to merge 1 commit into
ClickHouse:masterfrom
iliaal:fix/fixedstring-size-overflow

Conversation

@iliaal

@iliaal iliaal commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

ColumnFixedString::LoadBody sizes its buffer as string_size_ * rows with no overflow check. A server-declared FixedString(4611686018427387905) with 4 rows wraps to a 4-byte buffer. Size() then reports 0 rows, but At() still returns views of the full declared width, far past the end of the buffer.

Wrapping the column hides the mismatch. ColumnTuple::LoadBody and ColumnNullable::LoadBody never compare the loaded child sizes with the requested row count, and ColumnTuple::Size() reports only its first element. A Tuple(UInt8, FixedString(4611686018427387905)) or Nullable(FixedString(...)) block therefore loads "successfully" and reads out of bounds on access.

Fix: LoadBody throws ProtocolError when the multiplication overflows. Tuple and Nullable also throw when an element or the nested column ends up with a different row count than was loaded. With the FixedString check in place, no fresh column read from the wire reaches that second check today. It is defence in depth for element columns that misreport their size.

Reproducer: a Data block with 4 rows of Tuple(UInt8, FixedString(4611686018427387905)), or Nullable(FixedString(4611686018427387905)), followed by 4 bytes of FixedString payload.

Tests: ColumnsCase.FixedString_Load_SizeOverflow, Tuple_Load_FixedStringSizeOverflow, Tuple_Load_ElementSizeMismatch, Nullable_Load_FixedStringSizeOverflow and Nullable_Load_NestedSizeMismatch fail on master. Three valid round-trip controls pass on master and here.

ColumnFixedString::LoadBody sized its buffer as string_size_ * rows with no
overflow check, so a server-declared FixedString(4611686018427387905) with 4
rows wrapped to a 4-byte buffer: Size() then reported 0 rows while At()
returned views of the full declared width past the end of the buffer.
ColumnTuple::LoadBody and ColumnNullable::LoadBody did not notice, because
they never compare the loaded child sizes with the requested row count and
ColumnTuple::Size() reports only its first element.

LoadBody now throws a ProtocolError when the multiplication overflows. Tuple
and Nullable also throw when an element or the nested column holds a
different number of rows than was loaded; with the FixedString guard in place
no fresh column read from the wire reaches that check today, so it is defence
in depth for element columns that misreport their size (the tests use
Nothing, whose LoadBody appends to rows it already holds).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant