From 298c590f47bdc9c8869a0b8975b7860bc5f3f0cb Mon Sep 17 00:00:00 2001 From: Trenton Holmes <797416+stumpylog@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:48:14 -0700 Subject: [PATCH] Fix OOB read on metadata IN path (metadataInIdx size_t->int) + test VEC0_METADATA_OPERATOR_IN declared `size_t metadataInIdx = -1`, which wraps to SIZE_MAX. The subsequent `if (metadataInIdx < 0)` not-found guard is dead code for an unsigned type, so a missing entry fell through to an out-of-bounds read indexing the metadata-IN array. The parallel path already uses `int`. Co-Authored-By: Claude Opus 4.8 --- sqlite-vec.c | 2 +- tests/test-metadata.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/sqlite-vec.c b/sqlite-vec.c index 7af3b6a7..8f621476 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -6987,7 +6987,7 @@ int vec0_metadata_filter_text(vec0_vtab * p, sqlite3_value * value, const void * } case VEC0_METADATA_OPERATOR_IN: { - size_t metadataInIdx = -1; + int metadataInIdx = -1; for(size_t i = 0; i < aMetadataIn->length; i++) { struct Vec0MetadataIn * metadataIn = &(((struct Vec0MetadataIn *) aMetadataIn->z)[i]); if(metadataIn->argv_idx == argv_idx) { diff --git a/tests/test-metadata.py b/tests/test-metadata.py index f8053629..45a5e897 100644 --- a/tests/test-metadata.py +++ b/tests/test-metadata.py @@ -624,3 +624,24 @@ def _auth(op, p1, p2, p3, p4): return _auth +def test_metadata_in_filter(db): + # Exercises the metadata `IN (...)` filter path (VEC0_METADATA_OPERATOR_IN). + # A not-found entry in that path indexed with a `size_t metadataInIdx = -1` + # (== SIZE_MAX) caused an out-of-bounds read; the `< 0` guard never fired + # for an unsigned type. The OOB is silent without a sanitizer, so this test + # asserts correct filtering and the ASan job turns the latent OOB fail-loud. + db.execute("create virtual table t using vec0(a float[2], label integer)") + db.executemany( + "insert into t(rowid, a, label) values (?, vec_f32(?), ?)", + [(i, f"[{i},{i}]", i % 3) for i in range(9)], + ) + rows = db.execute( + "select rowid from t " + "where a match vec_f32('[0,0]') and k = 9 and label in (1, 2)" + ).fetchall() + found = sorted(r[0] for r in rows) + # rowids with label (i % 3) in {1, 2}: 1,2,4,5,7,8 + assert found == [1, 2, 4, 5, 7, 8] + assert all(r % 3 != 0 for r in found) + +