Add bounds checking on buffer_idx for constant_buffer and constant_data in XNNPACK#18820
Add bounds checking on buffer_idx for constant_buffer and constant_data in XNNPACK#18820
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/18820
Note: Links to docs will display an error until the docs builds have been completed. ❗ 1 Active SEVsThere are 1 currently active SEVs. If your PR is affected, please view them below: ❌ 2 New Failures, 2 Unrelated FailuresAs of commit 3f9dd63 with merge base 21d9c64 ( NEW FAILURES - The following jobs have failed:
BROKEN TRUNK - The following jobs failed but were present on the merge base:👉 Rebase onto the `viable/strict` branch to avoid these failures
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
This PR needs a
|
There was a problem hiding this comment.
Pull request overview
Adds bounds validation for buffer_idx when resolving constant tensor data in the XNNPACK flatbuffer path, preventing out-of-range access into constant_buffer / constant_data.
Changes:
- Add null + bounds checks for
flatbuffer_graph->constant_buffer()before indexing. - Add null + bounds checks for
flatbuffer_graph->constant_data()before callingGet(buffer_idx). - Emit error logs and return
nullptron invalid indices.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (cb == nullptr || buffer_idx >= cb->size()) { | ||
| ET_LOG( | ||
| Error, | ||
| "Invalid buffer_idx %u for constant_buffer of size %u", | ||
| buffer_idx, | ||
| cb ? cb->size() : 0); | ||
| return nullptr; | ||
| } |
There was a problem hiding this comment.
Returning nullptr on an out-of-bounds/nonexistent buffer_idx conflates “non-constant tensor” (buffer_idx==0) with “corrupt/invalid flatbuffer” (buffer_idx!=0 but invalid). Callers like defineTensor pass the returned pointer directly into xnn_define_tensor_value; with a non-zero constant_buffer_idx this can silently create an internal (non-constant) tensor instead of failing fast. Consider changing getConstantDataPtr to return a Result<const uint8_t*> (or add an explicit error flag) so invalid indices propagate as an Error, or at minimum enforce that buffer_idx!=0 implies non-null return and surface an error upstream.
| return nullptr; | ||
| } | ||
| const auto& constant_buffer = *cb; | ||
| return constant_buffer[buffer_idx]->storage()->data(); |
There was a problem hiding this comment.
Even after the bounds check, constant_buffer[buffer_idx] (and/or its storage field) can still be null in a malformed flatbuffer, which would crash when dereferenced. Please add null checks for constant_buffer[buffer_idx] and constant_buffer[buffer_idx]->storage() before calling ->data(), and treat failure as an invalid program rather than proceeding.
| return constant_buffer[buffer_idx]->storage()->data(); | |
| auto* buffer = constant_buffer[buffer_idx]; | |
| if (buffer == nullptr) { | |
| ET_LOG( | |
| Error, | |
| "Missing constant_buffer entry for buffer_idx %u", | |
| buffer_idx); | |
| return nullptr; | |
| } | |
| auto* storage = buffer->storage(); | |
| if (storage == nullptr) { | |
| ET_LOG( | |
| Error, | |
| "Missing constant_buffer storage for buffer_idx %u", | |
| buffer_idx); | |
| return nullptr; | |
| } | |
| return storage->data(); |
| cd ? cd->size() : 0); | ||
| return nullptr; | ||
| } | ||
| ConstantDataOffsetPtr constant_data_offset = cd->Get(buffer_idx); |
There was a problem hiding this comment.
cd->Get(buffer_idx) can still return nullptr if the vector contains a null offset (possible with corrupted input). constant_data_offset is dereferenced immediately afterward; add a null check for constant_data_offset and handle it as an invalid constant_data entry (log + propagate error).
| ConstantDataOffsetPtr constant_data_offset = cd->Get(buffer_idx); | |
| ConstantDataOffsetPtr constant_data_offset = cd->Get(buffer_idx); | |
| if (constant_data_offset == nullptr) { | |
| ET_LOG( | |
| Error, | |
| "Invalid null constant_data entry at buffer_idx %u", | |
| buffer_idx); | |
| return nullptr; | |
| } |
|
@claude review this PR |
|
Claude finished @lucylq's task in 1m 28s —— View job Review of PR #18820
The fix correctly addresses the core issue: the previous code dereferenced Two issues remain that should be addressed: 1. Missing null check after After the bounds check, const auto& constant_buffer = *cb;
auto* buffer_entry = constant_buffer[buffer_idx];
if (buffer_entry == nullptr || buffer_entry->storage() == nullptr) {
ET_LOG(Error, "Null constant_buffer entry at buffer_idx %u", buffer_idx);
return nullptr;
}
return buffer_entry->storage()->data();2. Missing null check after Similarly, ConstantDataOffsetPtr constant_data_offset = cd->Get(buffer_idx);
if (constant_data_offset == nullptr) {
ET_LOG(Error, "Null constant_data entry at buffer_idx %u", buffer_idx);
return nullptr;
}3. Caller impact of returning For the Items 1 and 2 are the higher priority since they leave crash paths open that this PR aims to close. Fix these issues → |
GregoryComer
left a comment
There was a problem hiding this comment.
Can we update defineTensor to handle the case of a nullptr returned from getConstantDataPtr? It might silently break on malformed PTE.
thanks that's a great catch - I'll wrap the return of |
Add bounds checking on buffer_idx in both constant_buffer and constant_data code paths of getConstantDataPtr to prevent out-of-bounds vector access from malicious flatbuffer inputs. Authored-with: Claude
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /** | ||
| Gets the constant data pointer associated with the given tensor value. | ||
| Obtaining the constant data pointer can either be from within the flatbuffer | ||
| payload (deprecated) or via offsets to the constant_data_ptr. If no constant | ||
| data associated with the tensor value, then returns nullptr. | ||
| */ | ||
| const uint8_t* getConstantDataPtr( | ||
| Result<const uint8_t*> getConstantDataPtr( |
There was a problem hiding this comment.
The doc comment is now inaccurate: this function returns Result<const uint8_t*>, not a raw pointer. Please update the comment to mention that failures are returned as an Error, and that the successful value may still be nullptr when the tensor has no constant data.
| buffer_idx < cb->size(), | ||
| InvalidProgram, | ||
| "buffer_idx %u out of bounds for constant_buffer of size %u", | ||
| buffer_idx, | ||
| cb->size()); |
There was a problem hiding this comment.
The format string uses %u for cb->size(), but flatbuffers::Vector::size() is size_t. This is undefined behavior on some platforms; use %zu (or cast to an unsigned type and keep %u) so the log formatting matches the argument type.
| buffer_idx < cd->size(), | ||
| InvalidProgram, | ||
| "buffer_idx %u out of bounds for constant_data of size %u", | ||
| buffer_idx, | ||
| cd->size()); |
There was a problem hiding this comment.
Same issue here: the log uses %u for cd->size() (a size_t). Please switch to %zu or cast appropriately to avoid varargs type mismatches/UB in the error path.
| weights_cache->load_unpacked_data(data_name); | ||
| if (!data_ptr.ok()) { | ||
| ET_LOG(Error, "Failed to load weights from cache"); | ||
| return nullptr; | ||
| return Error::InvalidProgram; | ||
| } |
There was a problem hiding this comment.
On weights-cache failures, this now always returns Error::InvalidProgram, even though load_unpacked_data() already returns a more specific error (e.g., InvalidExternalData). Consider returning data_ptr.error() (and optionally logging it) so callers can distinguish invalid models vs missing/invalid external data.
| data_name.c_str(), | ||
| static_cast<uint32_t>(buffer.error())); | ||
| return nullptr; | ||
| return Error::InvalidProgram; |
There was a problem hiding this comment.
This branch discards the underlying named_data_map->get_data() error and always returns Error::InvalidProgram. Propagating buffer.error() would preserve the real failure mode (e.g., NotFound/InvalidExternalData) and make error handling/debugging more accurate.
| return Error::InvalidProgram; | |
| return buffer.error(); |
Check that buffer_idx is within bounds of
flatbuffer_graph->constant_buffer()andflatbuffer_graph->constant_data(), whichever branch is chosenAuthored-with: Claude