Skip to content

filter_join_indices corrupts results and reads out of bounds past 2^31 candidate pairs #23827

Description

@VaggelisGian

filter_join_indices and its internal use by mixed joins index candidate pairs with 32-bit arithmetic and silently corrupt or read out of bounds when the equality-match candidate count exceeds 2^31, which column row limits do not prevent.

Candidate count is the hash-join match count, bounded only by memory (~8 bytes/pair), not by libcudf's int32 row limit: two ~47k-row tables sharing one distinct key already exceed 2^31 candidates. The left-join branch of the same file was deliberately made 64-bit for exactly this shape (comments at cpp/src/join/filter_join_indices/filter_join_indices.cu:237-288 cite "index vectors of size greater than integer limits"), but these sites were left 32-bit:

  • cpp/src/join/filter_join_indices/filter_join_indices_kernel.cuh:55: for (cudf::size_type i = tid; ...) truncates the int64 thread id; wrapped-negative values exit the loop via uint64 promotion, so candidate positions >= 2^31 are never evaluated and the tail of the uninitialized predicate_results buffer feeds downstream count/copy as garbage.
  • cpp/src/join/filter_join_indices/filter_join_indices.cu:179-180 (INNER count): static_cast<size_type>(left_indices.size()) wraps negative before iteration; count runs over a negative-distance range producing garbage counts.
  • cpp/src/join/filter_join_indices/filter_join_indices.cu:196 (INNER stencil): the counting_iterator<size_type> wraps at offset 2^31 inside CUB DeviceSelect::FlaggedIf; the lambda then indexes predicate_results_ptr[negative], an out-of-bounds read, and garbage flags can select more elements than the sized outputs, overrunning them.
  • Same pattern in the FULL branch: lines 321-322, 334-335, 362.

No guard rejects oversized inputs on any of these paths; only cross join has a CUDF_EXPECTS on result rows (cpp/src/join/cross_join.cu:51). The default public path (mixed_inner_join with no output_size) goes through the INNER branch, so production workloads hit this without opting into anything. A >2^31 candidate span with a predicate that rejects most candidates triggers every site while keeping the final result small enough to look healthy.

The newer output-size kernel shows the intended pattern (auto i = tid deducing thread_index_type, host reduction into std::size_t). Suggested fix: propagate that pattern to the predicate kernel loop and all INNER/FULL counting/stencil sites, mirroring the existing 64-bit left-join branch, plus a CUDF_EXPECTS capping the post-filter result count at size_type max before materializing columns, matching cross join.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions