Validate Panorama batch_size and codes/cum_sums on deserialization (#5438)

Summary:
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5438

The `IndexFlatPanorama` (`IxFP`/`IxFp`) and `IndexIVFFlatPanorama`
(`IwP2`) deserialization branches read fields that are trusted as
invariants at reconstruct time but are never validated:

- `batch_size` is read straight from the stream and used as a divisor in
  `Panorama::reconstruct` (`key / batch_size`, `key % batch_size`). A
  crafted index with `batch_size == 0` raises SIGFPE. The sibling `ilp2`
  branch already guards this exact field; the IxFP/IxFp and IwP2 branches
  did not.

- `codes`/`cum_sums` are read independently of `ntotal`, so a forged
  `ntotal > 0` with an empty (or undersized) `codes`/`cum_sums` vector
  passes deserialization and then reads through a null/out-of-bounds
  pointer in `Panorama::reconstruct`'s `memcpy`. Every non-Panorama flat
  reader already enforces the analogous size invariant.

Adds a `batch_size > 0` guard (both branches) and a codes/cum_sums size
check that mirrors the whole-batch padding layout in
`IndexFlatPanorama::add`. Converts the crashes into clean FaissExceptions.
Found by agentic fuzzing.

Reviewed By: trang-nm-nguyen

Differential Revision: D112368332

fbshipit-source-id: 76bb667bc69a5ac048234fa0b4f51a924f1ae1c2
This commit is contained in:
Michael Norris
2026-07-17 10:17:21 -07:00
committed by meta-codesync[bot]
parent d43336302e
commit fba9084a62
+21
View File
@@ -1617,6 +1617,8 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
READ1(n_levels);
FAISS_THROW_IF_NOT_FMT(n_levels > 0, "invalid n_levels %zd", n_levels);
READ1(batch_size);
FAISS_THROW_IF_NOT_FMT(
batch_size > 0, "invalid IxFP batch_size %zd", batch_size);
std::unique_ptr<IndexFlatPanorama> idxp;
if (h == fourcc("IxFP")) {
idxp = std::make_unique<IndexFlatL2Panorama>(
@@ -1629,6 +1631,21 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
READ1_BOOL(idxp->is_trained);
READVECTOR(idxp->codes);
READVECTOR(idxp->cum_sums);
size_t num_slots = mul_no_overflow(
((size_t)idxp->ntotal + idxp->batch_size - 1) /
idxp->batch_size,
idxp->batch_size,
"IndexFlatPanorama num_batches*batch_size");
FAISS_THROW_IF_NOT(
idxp->codes.size() ==
mul_no_overflow(
num_slots, idxp->code_size, "IndexFlatPanorama codes"));
FAISS_THROW_IF_NOT(
idxp->cum_sums.size() ==
mul_no_overflow(
num_slots,
idxp->pano.n_levels + 1,
"IndexFlatPanorama cum_sums"));
idxp->verbose = false;
idx = std::move(idxp);
} else if (
@@ -2044,6 +2061,10 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
ivfp->code_size = ivfp->d * sizeof(float);
READ1(ivfp->n_levels);
READ1(ivfp->batch_size);
FAISS_THROW_IF_NOT_FMT(
ivfp->batch_size > 0,
"invalid IwP2 batch_size %zd",
ivfp->batch_size);
read_InvertedLists(*ivfp, f, io_flags);
idx = std::move(ivfp);
} else if (h == fourcc("IwFl")) {