Skip to content

Commit 3ccd090

Browse files
scsiguyfacebook-github-bot
authored andcommitted
Reject null quantizer during IVF index deserialization (facebookresearch#5112)
Summary: Add validation in read_ivf_header() to reject a null quantizer sub-index read from serialized data. The IVF deserialization reads the quantizer via read_index(), which returns nullptr when the stream contains the "null" fourcc. A null quantizer is fundamentally invalid for any IVF index type. Without this check, downstream code (e.g. initialize_IVFPQ_precomputed_table, IndexIVF::search) dereferences the null pointer. This single validation protects all IVF index types that share read_ivf_header: IndexIVFFlat, IndexIVFPQ, IndexIVFScalarQuantizer, IndexIVFAdditiveQuantizer, and others. Reviewed By: mnorris11 Differential Revision: D101236489
1 parent a825131 commit 3ccd090

3 files changed

Lines changed: 62 additions & 0 deletions

File tree

faiss/IndexIVF.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ void Level1Quantizer::train_q1(
5858
const float* x,
5959
bool verbose,
6060
MetricType metric_type) {
61+
FAISS_THROW_IF_NOT_MSG(quantizer, "IVF quantizer must not be null");
6162
size_t d = quantizer->d;
6263
if (quantizer->is_trained &&
6364
(static_cast<size_t>(quantizer->ntotal) == nlist)) {
@@ -188,6 +189,7 @@ void IndexIVF::add(idx_t n, const float* x) {
188189
}
189190

190191
void IndexIVF::add_with_ids(idx_t n, const float* x, const idx_t* xids) {
192+
FAISS_THROW_IF_NOT_MSG(quantizer, "IVF quantizer must not be null");
191193
FAISS_THROW_IF_NOT_MSG(invlists, "IVF index has no inverted lists");
192194
std::unique_ptr<idx_t[]> coarse_idx(new idx_t[n]);
193195
quantizer->assign(n, x, coarse_idx.get());
@@ -309,6 +311,7 @@ void IndexIVF::search(
309311
idx_t* labels,
310312
const SearchParameters* params_in) const {
311313
FAISS_THROW_IF_NOT(k > 0);
314+
FAISS_THROW_IF_NOT_MSG(quantizer, "IVF quantizer must not be null");
312315
FAISS_THROW_IF_NOT_MSG(invlists, "IVF index has no inverted lists");
313316
const IVFSearchParameters* params = nullptr;
314317
if (params_in) {
@@ -734,6 +737,7 @@ void IndexIVF::range_search(
734737
float radius,
735738
RangeSearchResult* result,
736739
const SearchParameters* params_in) const {
740+
FAISS_THROW_IF_NOT_MSG(quantizer, "IVF quantizer must not be null");
737741
const IVFSearchParameters* params = nullptr;
738742
const SearchParameters* quantizer_params = nullptr;
739743
if (params_in) {

faiss/IndexIVFPQ.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ void initialize_IVFPQ_precomputed_table(
388388
AlignedTable<float>& precomputed_table,
389389
bool by_residual,
390390
bool verbose) {
391+
FAISS_THROW_IF_NOT_MSG(quantizer, "IVF quantizer must not be null");
391392
size_t nlist = quantizer->ntotal;
392393
size_t d = quantizer->d;
393394
FAISS_THROW_IF_NOT(d == pq.d);

tests/test_read_index_deserialize.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <faiss/IndexIVFAdditiveQuantizerFastScan.h>
2323
#include <faiss/IndexIVFFlat.h>
2424
#include <faiss/IndexIVFIndependentQuantizer.h>
25+
#include <faiss/IndexIVFPQ.h>
2526
#include <faiss/IndexIVFPQR.h>
2627
#include <faiss/IndexRaBitQFastScan.h>
2728
#include <faiss/VectorTransform.h>
@@ -1647,6 +1648,62 @@ TEST(ReadIndexDeserialize, IVFQuantizerUntrained) {
16471648
EXPECT_NO_THROW(read_index_up(&reader));
16481649
}
16491650

1651+
// -----------------------------------------------------------------------
1652+
// Test: initialize_IVFPQ_precomputed_table rejects a null quantizer.
1653+
// Protects against null-deref from corrupt serialized data where the
1654+
// quantizer sub-index is absent (fourcc "null").
1655+
// -----------------------------------------------------------------------
1656+
TEST(ReadIndexDeserialize, IVFPQNullQuantizerPrecomputeTableRejected) {
1657+
ProductQuantizer pq(4, 1, 8);
1658+
AlignedTable<float> precomputed_table;
1659+
int use_precomputed_table = 0;
1660+
EXPECT_THROW(
1661+
initialize_IVFPQ_precomputed_table(
1662+
use_precomputed_table,
1663+
/*quantizer=*/nullptr,
1664+
pq,
1665+
precomputed_table,
1666+
/*by_residual=*/true,
1667+
/*verbose=*/false),
1668+
faiss::FaissException);
1669+
}
1670+
1671+
TEST(ReadIndexDeserialize, IVFNullQuantizerSearchRejected) {
1672+
IndexIVFFlat ivf;
1673+
ivf.quantizer = nullptr;
1674+
ivf.is_trained = true;
1675+
std::vector<float> x(4);
1676+
std::vector<float> distances(1);
1677+
std::vector<idx_t> labels(1);
1678+
EXPECT_THROW(
1679+
ivf.search(1, x.data(), 1, distances.data(), labels.data()),
1680+
faiss::FaissException);
1681+
}
1682+
1683+
TEST(ReadIndexDeserialize, IVFNullQuantizerRangeSearchRejected) {
1684+
IndexIVFFlat ivf;
1685+
ivf.quantizer = nullptr;
1686+
ivf.is_trained = true;
1687+
std::vector<float> x(4);
1688+
RangeSearchResult result(1);
1689+
EXPECT_THROW(
1690+
ivf.range_search(1, x.data(), 1.0, &result), faiss::FaissException);
1691+
}
1692+
1693+
TEST(ReadIndexDeserialize, IVFNullQuantizerAddRejected) {
1694+
IndexIVFFlat ivf;
1695+
ivf.quantizer = nullptr;
1696+
std::vector<float> x(4);
1697+
EXPECT_THROW(ivf.add(1, x.data()), faiss::FaissException);
1698+
}
1699+
1700+
TEST(ReadIndexDeserialize, IVFNullQuantizerTrainRejected) {
1701+
IndexIVFFlat ivf;
1702+
ivf.quantizer = nullptr;
1703+
std::vector<float> x(4);
1704+
EXPECT_THROW(ivf.train(1, x.data()), faiss::FaissException);
1705+
}
1706+
16501707
// -----------------------------------------------------------------------
16511708
// VectorTransform deserialization validation tests
16521709
// -----------------------------------------------------------------------

0 commit comments

Comments
 (0)