Skip to content

fix(hnsw): reject build dims whose vector count overflows size_t#333

Merged
singaraiona merged 2 commits into
RayforceDB:devfrom
belowzeroff:fix/hnsw-build-overflow
Jul 18, 2026
Merged

fix(hnsw): reject build dims whose vector count overflows size_t#333
singaraiona merged 2 commits into
RayforceDB:devfrom
belowzeroff:fix/hnsw-build-overflow

Conversation

@belowzeroff

@belowzeroff belowzeroff commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

What & why

ray_hnsw_build sized the copied vector block as n_nodes * dim * sizeof(float)
with no overflow check (src/store/hnsw.c). Dimensions whose product wraps
size_t under-allocate the copy, while the following memcpy — and every later
distance read (vectors + id*dim) during graph construction — runs past the
buffer.

The per-layer neighbor arrays in the loader already carry a divide-based
overflow guard; the build path had none. This adds the same guard at the top of
ray_hnsw_build, rejecting overflowing dimensions before any allocation:

if ((uint64_t)n_nodes > SIZE_MAX / sizeof(float) / (uint64_t)dim) return NULL;

n_nodes and dim are already validated > 0 at that point, so the divide is
safe.

Scope / severity: this hardens the public C API boundary. The in-tree
(hnsw-build ...) builtin sizes its vectors from an in-memory list and cannot
reach the overflow, so for the shipped language surface this is defense-in-depth
rather than a remotely reachable bug. It is the build-path companion to the
loader's untrusted-file vectors guard (submitted separately).

Test

embedding/hnsw_build_overflow_rejected drives an overflowing n_nodes/dim
pair (chosen so the byte count wraps to 16) and asserts the build is refused.
Verified as a genuine regression test: with the guard removed, the debug ASan
build faults at the copy —

SUMMARY: AddressSanitizer: stack-buffer-overflow hnsw.c:494 in ray_hnsw_build

With the guard in place the call returns NULL and the source is never read.

Full suite under the debug ASan + UBSan build: 3632 of 3633 passed (1 skipped, 0 failed).

Checklist

  • PR targets dev (not master)
  • Commits follow Conventional Commits (fix:)
  • make builds cleanly (no new warnings)
  • make test passes; tests added/updated for behaviour changes

belljack and others added 2 commits July 18, 2026 15:49
ray_hnsw_build sized the copied vector block as n_nodes * dim *
sizeof(float) with no overflow check. Dimensions whose product wraps
size_t under-allocate the copy while the memcpy — and every later distance
read (vectors + id*dim) — run past the buffer. Guard the product before any
allocation, mirroring the per-layer neighbor guard in the loader, and
reject overflowing dimensions.

This hardens the public C API boundary; the in-tree (hnsw-build ...) path
sizes vectors from an in-memory list and cannot reach the overflow, so it
is defense-in-depth.

Add a regression test driving an overflowing n_nodes/dim pair; with the
guard removed it faults under ASan (stack-buffer-overflow at the copy).
@singaraiona
singaraiona merged commit cb1c6cd into RayforceDB:dev Jul 18, 2026
9 checks passed
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.

3 participants