Skip to content

Fix UBSan misaligned AggregateState access in hash aggregate scan - #1004

Closed
adsharma wants to merge 2 commits into
mainfrom
fix/ubsan-aggregate-state-scan
Closed

adsharma wants to merge 2 commits into
mainfrom
fix/ubsan-aggregate-state-scan

Conversation

@adsharma

Copy link
Copy Markdown
Contributor

Fixes the ASan+UBSan nightly failure (map_aggregate.cpp:106: member access within misaligned address ... for type 'struct AggregateState').

Root cause: factorized-table tuples are packed without alignment padding, so an aggregate state column can start at an odd offset (e.g. after a small group-by key). HashAggregateScan reinterpreted those bytes as AggregateState* and invoked virtuals on the misaligned pointer:

  • writeAggResultWithoutNullToVector (aggregateState->writeToVector()) loads the vptr from a misaligned address; the WithNull path has the same problem via constCast<AggregateStateWithNull>().isNull.
  • hash_aggregate_scan.cpp: offset += aggState->getStateSize() is a virtual call on the same misaligned pointer.

Fix (follows the memcpy precedent of fafc362/2092c4284):

  • map_aggregate.cpp: capture each aggregate's state size at plan time and copy the state bytes into an aligned thread-local buffer before any virtual dispatch in the move-to-vector funcs. The copy carries the vptr bit pattern, so the dynamic type is preserved; thread-local keeps it race-free across scan threads.
  • hash_aggregate_scan.cpp: advance the scan offset using the table schema's state column sizes (sized with AggregateFunction::getAggregateStateSize() at plan time) instead of the virtual getStateSize().

Nightly run: https://github.com/LadybugDB/ladybug/actions/runs/35438024832/job/105947162035

Note: that log contains further reports of the same class of bug in other aggregate states (sum.h, min_max.h, collect.cpp, histogram.cpp, percentile_*.cpp update/combine paths, base_hash_table.cpp list_t). Left for follow-up PRs to keep this one focused.

Factorized-table tuples are packed without alignment padding, so an
aggregate state column can start at an odd offset (e.g. after a small
group-by key). HashAggregateScan reinterpreted those bytes as
AggregateState* and HashAggregateScan/getMoveAggResultToVectorFuncs
invoked virtuals on it:

- map_aggregate.cpp:106: aggregateState->writeToVector() loads the vptr
  from a misaligned address ("member access within misaligned address
  ... for type 'struct AggregateState', which requires 8 byte
  alignment"), and the WithNull path has the same problem via
  constCast<AggregateStateWithNull>().isNull.
- hash_aggregate_scan.cpp: offset += aggState->getStateSize() is a
  virtual call on the same misaligned pointer.

Fix, following the memcpy precedent of fafc362/2092c4284:

- Capture each aggregate's state size at plan time and copy the state
  bytes into an aligned thread-local buffer before any virtual
  dispatch in the move-to-vector funcs.
- Advance the scan offset using the table schema's state column sizes
  instead of the virtual getStateSize().

Nightly run: https://github.com/LadybugDB/ladybug/actions/runs/35438024832/job/105947162035
…438024832

Same bug class as the scan-path fix: packed/unaligned storage read
through typed pointers.

- function/aggregate_function.h: route updateAll/updatePos/combine/
  finalize through an aligned thread-local staging buffer when the
  state pointer fails a max_align_t check (covers SumState, AvgState,
  MinMaxState, CollectState, HistogramState, PercentileCont/DiscState
  update/combine/finalize paths at a single choke point; aligned
  states go straight through).
- common/in_mem_overflow_buffer.cpp: keep bump allocations 8-byte
  aligned (COLLECT/HISTOGRAM linked-list elements store pointers).
- processor/result/base_hash_table.cpp: memcpy list_t out of packed
  tuples before touching members (both list compare entry points).
- function/find_function.cpp: memcpy word-sized needle/haystack
  comparisons (haystack+offset is inherently misaligned).
- parquet interval_column_reader.cpp: memcpy interval fields out of
  the unaligned plain-data buffer.

Still open (different bug class, needs semantic decisions): integer
overflow/negation/shift reports (int128_t.cpp, hash_aggregate.h
shift-64, std::abs, compression.cpp negation).
@adsharma

Copy link
Copy Markdown
Contributor Author

Addressed via #1014

@adsharma adsharma closed this Sep 23, 2026
@adsharma
adsharma deleted the fix/ubsan-aggregate-state-scan branch September 23, 2026 22:13
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