Skip to content

Guard against integer overflow in index deserialization storage-size checks#5313

Closed
AgentGymLeader wants to merge 1 commit into
facebookresearch:mainfrom
AgentGymLeader:harden-flat-index-overflow
Closed

Guard against integer overflow in index deserialization storage-size checks#5313
AgentGymLeader wants to merge 1 commit into
facebookresearch:mainfrom
AgentGymLeader:harden-flat-index-overflow

Conversation

@AgentGymLeader

Copy link
Copy Markdown
Contributor

What

Several index deserialization readers validate the backing store with <vector>.size() == ntotal * element_size, where the multiplication is performed in size_t and is unchecked. read_index_header accepts any non-negative ntotal (it bounds d but not ntotal), so a crafted index can make ntotal * code_size wrap modulo 2^64 to a small value. The equality check then passes against a small backing vector, and an internally inconsistent index object (huge ntotal, tiny backing storage) is accepted.

Downstream, code that trusts ntotal reads past the backing vector — e.g. IndexFlat::reconstruct(key, ...) checks only key < ntotal before memcpy(recons, &codes[key * code_size], code_size), and the IVF/flat scan loops index into codes/xb by i * code_size. The result is an out-of-bounds read (crash / DoS, potential information disclosure) when an application loads an untrusted index file.

Fix

Wrap the ntotal * element_size multiplications in the existing mul_no_overflow helper (faiss/impl/FaissAssert.h), which throws on overflow instead of wrapping. This brings the flat / binary-flat / quantizer readers and the NNDescent final_graph check to parity with the Array/Block inverted-list readers in this same file, which already use mul_no_overflow for their deserialization size checks. Well-formed indexes are unaffected — the guard only rejects inputs whose declared sizes overflow.

Sites hardened (faiss/impl/index_read.cpp): IndexFlat, IndexLSH, IndexPQ, IndexResidualQuantizer, IndexLocalSearchQuantizer, IndexProductResidualQuantizer, IndexProductLocalSearchQuantizer, IndexBinaryFlat, and NNDescent. Bare == ntotal / == ntotal + 1 checks (no multiplication) are left unchanged.

Verification

  • mul_no_overflow and FAISS_THROW_IF_NOT are already used throughout this file; the change follows the existing call pattern.
  • No behavioral change for valid indexes; the new guards only fire on malformed inputs whose ntotal * element_size would overflow size_t.

…checks

The flat-index, binary-flat, and quantizer readers validated
codes.size() == ntotal * code_size (and final_graph.size() ==
ntotal * K for NNDescent) with unchecked size_t multiplications
that could wrap for a crafted large ntotal, causing the check to
pass while ntotal is inconsistent with the actual backing storage.

Now uses mul_no_overflow() at all 9 sites to reject overflowing
products and throw instead of silently accepting a malformed
index, matching the Array/Block inverted-list reader hardening
already present in this file (22 existing call sites -> 31).
@meta-cla meta-cla Bot added the CLA Signed label Jun 14, 2026
@meta-codesync

meta-codesync Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

@alibeklfc has imported this pull request. If you are a Meta employee, you can view this in D108628013.

@meta-codesync meta-codesync Bot closed this in 6f62d01 Jun 15, 2026
@meta-codesync

meta-codesync Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

@alibeklfc merged this pull request in 6f62d01.

@meta-codesync meta-codesync Bot added the Merged label Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant