Bound file-controlled sizes in view(): levels region and dense vectors span (OOB read on crafted index) - #776
Open
professor-moody wants to merge 1 commit into
Conversation
…s span index_gt::view walked the memory-mapped levels[] array (sized from the file's header.size) before the only file-size check, and index_dense::view formed a matrix_rows*matrix_cols span into the mapping with no bound plus an unsigned-underflow header guard. A crafted .usearch index over-read the mapping. Bound the levels region before the offset-precompute loop, bound the vectors span with a checked multiply, and make the header guard underflow-safe. Validated with AddressSanitizer (crafted rejected cleanly, valid indexes still load).
Author
|
Small correction to the Impact section of this PR: I named a Python entry point that does not exist. I wrote The entry points that actually memory-map, and therefore reach
Nothing else changes. The two sinks and the fix are unaffected. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
index_gt::view(andindex_dense::view) read file-controlled sizes and dereference the memory mapping before bounding them, so a crafted.usearchindex over-reads the mapping. Two sinks:Base
view()levels region (index.hpp):viewsizes a memory-mappedlevels[]array from the file'sheader.sizeand walks it in the offset-precompute loop before theif (file.size() < total_bytes)check runs. A crafted header declaring more nodes than the file contains readslevels[]past the mapping. Fix: reject when the file does not extend tofirst_offset(the end of the levels array) before the loop.Dense
view()vectors span (index_dense.hpp):vectors_buffer = {file.data() + offset, matrix_rows * matrix_cols}forms a span with no file-size check (and the product can overflowsize_t), and the followingif (file.size() - offset < sizeof(buffer))guard is an unsigned subtraction that underflows onceoffsetpassesfile.size(). Fix: bound the span with a checked multiply and make the header guard underflow-safe.Impact
Memory-mapping a crafted or untrusted
.usearchindex viaIndex.view/Index.load(..., view=True)yields an out-of-bounds read (crash / adjacent-memory disclosure). CWE-125.Validation
AddressSanitizer, control vs crafted, driving the real
view()over a buffer-backed mapping: a well-formed index loads cleanly; a crafted one (header.sizelarger than the file / oversized matrix dims) is now rejected with a clear error and no OOB. Found with a structure-aware model/index parser fuzzer.