Repository navigation
Conversation
Inserting a row into vec_static_blobs copies its name with sqlite3_mprintf() into the connection's registry, and the module freed the registry with sqlite3_free() alone when the connection closed, so every registered name leaked. LeakSanitizer reports it from vec_static_blobsUpdate() for any connection that registers a static blob, as test_vec_static_blob_entries does. The module's destructor now frees each registered name before the registry.
vec_static_blob_entriesFilter() rounds the blob's vector count up to a multiple of 8 (bsize) for its candidate bitmap, then clears the bits past the last vector with a loop that starts at bsize itself, one bit past the bitmap's end. When the vector count fills the bitmap's bytes exactly, as 64 vectors do, SQLite's allocator has no padding to absorb the write, and AddressSanitizer reports a heap-buffer-overflow in bitmap_set() from the filter. The loop now clears the bits from the vector count up to bsize. test_vec_static_blob_entries_knn_with_whole_candidate_bytes runs a KNN query over a 64-vector blob and checks its rows; against an AddressSanitizer build it reports the overflow without the fix and passes with it.
vec_static_blob_entriesFilter() allocates a KNN query's rowids and distances into the cursor's knn_data, and vec_static_blob_entriesClose() freed knn_data alone, so every KNN query on a vec_static_blob_entries table leaked both arrays. LeakSanitizer reports them from the filter. The cursor's close now calls sbe_query_knn_data_clear() first, which frees them, as photostructure/sqlite-vec f56fdeb does.
|
Thanks, all three fixes check out here: the off-by-one reproduces on main under AddressSanitizer as a one-byte read past the candidate bitmap for a 64-vector blob, the destructor is the right hook for the names, and One leak in the same family remains, and since this PR is titled "two leaks in static blobs" I would rather close it here than in a follow-up. I cannot push to your branch (your repository is a fork of asg017/sqlite-vec rather than of this one, so GitHub never grants the maintainer-edit permission here), so here is the change, for the top of // A cursor can be filtered again without being closed, as the inner loop
// of a join or once per value of k IN (...): free the previous results.
if (pCur->knn_data) {
sbe_query_knn_data_clear(pCur->knn_data);
sqlite3_free(pCur->knn_data);
pCur->knn_data = NULL;
}If you would rather, I can put it on a branch here for you to pull, with a test that runs the Noted but not asked for: |
Three memory errors in the static blob modules, one commit each.
test_vec_static_blob_entriesregisters blobs and runs a KNN query, but over a 5-vector blob, where the overflow lands in the allocator's padding, and no run of the suite checks for leaks.Leak of the registered names. Inserting a row into
vec_static_blobscopies its name withsqlite3_mprintf()into the connection's registry, and the module freed the registry withsqlite3_free()alone when the connection closed, so every registered name leaked. The module's destructor now frees each registered name before the registry.Overflow.
vec_static_blob_entriesFilter()rounds the blob's vector count up to a multiple of 8 (bsize) for its candidate bitmap, then clears the bits past the last vector with a loop that starts atbsizeitself, one bit past the bitmap's end, in every KNN query on a static blob. SQLite's allocator rounds the request up only in builds that keep memory statistics (the default), which hides the write there for small blobs; withSQLITE_DEFAULT_MEMSTATUS=0it lands outside the allocation for any vector count, as mceachen found. At 64 vectors the bitmap is exactly 8 bytes, and AddressSanitizer reports a heap-buffer-overflow inbitmap_set()from the filter:The loop now clears the bits from the vector count up to
bsize.Leak of a KNN's results.
vec_static_blob_entriesFilter()allocates a KNN query's rowids and distances into the cursor'sknn_data, andvec_static_blob_entriesClose()freedknn_dataalone, so every KNN query on avec_static_blob_entriestable leaked both arrays. The close now callssbe_query_knn_data_clear(), which was defined but not called, as photostructure/sqlite-vec@f56fdeb does.A C program that registers a 64-vector blob, runs that query and closes the connection, built with
-fsanitize=addressagainst this tree:bitmap_set();vec_static_blobsUpdate(), and the KNN's rowids and distances fromvec_static_blob_entriesFilter();test_vec_static_blob_entries_knn_with_whole_candidate_bytesruns the query from Python and checks its rows; against an AddressSanitizer build (LD_PRELOADof libasan) it reports the overflow on main and passes here. The full suite passes (144 passed, 4 skipped, 259 snapshots), as do the C unit tests, andtest-loadable.pypasses against the AddressSanitizer build.It merges cleanly with #9 through #14, #17, #18 and #20 through #24. The first two fixes landed on photostructure/sqlite-vec from photostructure#3, as photostructure/sqlite-vec@01c827e and photostructure/sqlite-vec@b02677e; that fork already had the third. asg017/sqlite-vec has removed the static blob modules, so these have no upstream twin.