Skip to content

Commit eccb3ad

Browse files
scsiguyfacebook-github-bot
authored andcommitted
Validate IndexHNSW2Level storage type during deserialization and search (facebookresearch#5113)
Summary: Add validation that IndexHNSW2Level (fourcc "IHN2") has storage of an appropriate type, both at deserialization time and at search time. IndexHNSW2Level::search() uses dynamic_cast to dispatch between Index2Layer and IndexIVFPQ storage types. When storage is null or a different type (e.g. IndexFlat from corrupt serialized data, or a programmatically misconfigured index), the dynamic_cast returns nullptr which is then unconditionally dereferenced, causing a segfault. Deserialization-time fix: After reading the HNSW storage sub-index for IHN2, validate that storage is non-null and is either Index2Layer or IndexIVFPQ. Search-time defense-in-depth: Add a FAISS_THROW_IF_NOT check on the dynamic_cast result in IndexHNSW2Level::search() before dereferencing. This protects against programmatically constructed indexes that bypass deserialization validation. Reviewed By: mnorris11 Differential Revision: D101243603
1 parent 75426f0 commit eccb3ad

3 files changed

Lines changed: 44 additions & 0 deletions

File tree

faiss/IndexHNSW.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,9 @@ void IndexHNSW2Level::search(
865865

866866
const IndexIVFPQ* index_ivfpq =
867867
dynamic_cast<const IndexIVFPQ*>(storage);
868+
FAISS_THROW_IF_NOT_MSG(
869+
index_ivfpq,
870+
"IndexHNSW2Level mixed search requires IndexIVFPQ storage");
868871

869872
int nprobe = index_ivfpq->nprobe;
870873

faiss/impl/index_read.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,6 +1982,15 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
19821982
idxhnsw->storage->d,
19831983
idxhnsw->d);
19841984
}
1985+
if (h == fourcc("IHN2")) {
1986+
FAISS_THROW_IF_NOT_MSG(
1987+
idxhnsw->storage,
1988+
"IndexHNSW2Level requires non-null storage");
1989+
FAISS_THROW_IF_NOT_MSG(
1990+
dynamic_cast<Index2Layer*>(idxhnsw->storage) ||
1991+
dynamic_cast<IndexIVFPQ*>(idxhnsw->storage),
1992+
"IndexHNSW2Level storage must be Index2Layer or IndexIVFPQ");
1993+
}
19851994
if (h == fourcc("IHNp") && !(io_flags & IO_FLAG_PQ_SKIP_SDC_TABLE)) {
19861995
auto* storage_pq = dynamic_cast<IndexPQ*>(idxhnsw->storage);
19871996
FAISS_THROW_IF_NOT_MSG(

tests/test_read_index_deserialize.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,38 @@ TEST(ReadIndexDeserialize, HNSWValidNeighborsSearchWorks) {
14671467
EXPECT_LT(labels[0], ntotal);
14681468
}
14691469

1470+
// -----------------------------------------------------------------------
1471+
// Test: IndexHNSW2Level with wrong storage type is rejected.
1472+
// Protects against corrupt serialized data where storage is not
1473+
// Index2Layer or IndexIVFPQ, causing null-deref from failed dynamic_cast.
1474+
// -----------------------------------------------------------------------
1475+
TEST(ReadIndexDeserialize, HNSW2LevelWrongStorageType) {
1476+
// Build an IHN2 with IndexFlat storage (wrong type — must be
1477+
// Index2Layer or IndexIVFPQ).
1478+
std::vector<uint8_t> buf;
1479+
push_fourcc(buf, "IHN2");
1480+
push_index_header(buf, /*d=*/4, /*ntotal=*/0);
1481+
push_minimal_hnsw(buf, /*ntotal=*/0);
1482+
// IndexFlat storage — wrong type for HNSW2Level
1483+
push_minimal_flat(buf, /*d=*/4, /*ntotal=*/0);
1484+
1485+
expect_read_throws_with(buf, "Index2Layer or IndexIVFPQ");
1486+
}
1487+
1488+
// -----------------------------------------------------------------------
1489+
// Test: IndexHNSW2Level with null storage is rejected.
1490+
// -----------------------------------------------------------------------
1491+
TEST(ReadIndexDeserialize, HNSW2LevelNullStorage) {
1492+
std::vector<uint8_t> buf;
1493+
push_fourcc(buf, "IHN2");
1494+
push_index_header(buf, /*d=*/4, /*ntotal=*/0);
1495+
push_minimal_hnsw(buf, /*ntotal=*/0);
1496+
// Null storage
1497+
push_fourcc(buf, "null");
1498+
1499+
expect_read_throws_with(buf, "non-null storage");
1500+
}
1501+
14701502
// -----------------------------------------------------------------------
14711503
// Test: NSG ntotal != index ntotal.
14721504
// -----------------------------------------------------------------------

0 commit comments

Comments
 (0)