-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fix use-after-free of unpacked constants in the XNNPACK weights cache #22779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
JakeStevens
merged 3 commits into
pytorch:main
from
msluszniak:ms/xnnpack-weights-cache-unpacked-uaf
Sep 18, 2026
+116
−2
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -215,6 +215,18 @@ Error XNNWeightsCache::initialize_for_runtime( | |
| named_data_map_ = named_data_map; | ||
| is_finalized_ = false; | ||
|
|
||
| // An earlier compile can bail out between its first load_unpacked_data and | ||
| // its finalize_for_runtime, leaving its constants here. A successful compile | ||
| // frees whatever is left in finalize_for_runtime, but a failed one never | ||
| // reaches it, so back to back failures would pile up on an instance that | ||
| // lives as long as the process. The model that loaded them is gone, so free | ||
| // them here. | ||
| for (FreeableBuffer& buffer : unpacked_data_) { | ||
| buffer.Free(); | ||
| } | ||
| unpacked_data_.clear(); | ||
| unpacked_data_to_name_.clear(); | ||
|
|
||
| #ifndef _WIN32 | ||
| if (packed_cache_path_.empty() || packed_file_fd_ >= 0) { | ||
| return Error::Ok; | ||
|
|
@@ -296,11 +308,35 @@ Error XNNWeightsCache::initialize_for_runtime( | |
| return Error::Ok; | ||
| } | ||
|
|
||
| void XNNWeightsCache::take_unpacked_data_from( | ||
| size_t first_index, | ||
| std::vector<FreeableBuffer>& out) { | ||
| // No bounds check: the loop below is empty when first_index is at or past | ||
| // the end, and the list only grows during a compile so that cannot happen | ||
| // anyway. An index that is too small cannot be detected here, and would hand | ||
| // the executor buffers of values XNNPACK did pack; callers must pass the | ||
| // get_num_unpacked_data() taken immediately before the value was defined. | ||
| for (size_t i = first_index; i < unpacked_data_.size(); i++) { | ||
| // The name map is keyed on the data pointer, which a move preserves, so | ||
| // look_up_or_insert keeps naming packed entries correctly after this. | ||
| out.push_back(std::move(unpacked_data_[i])); | ||
| } | ||
| // The moved-from entries stay in the list rather than being erased: | ||
| // FreeableBuffer deletes move assignment, so vector::erase does not compile, | ||
| // and a moved-from buffer holds a null pointer, which makes the Free() in | ||
| // finalize_for_runtime a no-op. Leaving them also keeps the indices handed | ||
| // out by get_num_unpacked_data() stable across calls. | ||
| } | ||
|
|
||
| Result<std::vector<std::string>> XNNWeightsCache::finalize_for_runtime() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The description and the repro table still describe the first version, where finalize handed the buffers back. The version in the tree frees everything left and retains per value instead, so the numbers no longer match the code. Please update them. |
||
| is_finalized_ = true; | ||
|
|
||
| // All data has been packed by create_runtime | ||
| // so we clear the unpacked data as it is no longer needed | ||
| // Everything still here belongs to a value XNNPACK packed, so create_runtime | ||
| // has copied it into the packed region and the unpacked copy can go. Buffers | ||
| // for values XNNPACK does not pack (PReLU slopes, for example) were handed to | ||
| // the executor by take_unpacked_data_from while the graph was being built; | ||
| // the subgraph keeps pointers into those, so freeing them here would leave | ||
| // the runtime reading freed memory. | ||
| for (FreeableBuffer& buffer : unpacked_data_) { | ||
|
msluszniak marked this conversation as resolved.
|
||
| buffer.Free(); | ||
| } | ||
|
|
||
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
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
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some tests can still be provided. Load two named constants, take the count between them, take from that index, finalize, then check the taken buffer is still readable and the other is gone. A second one for the new cleanup: load a constant, skip finalize, initialize again, check the list is empty. Both fail on the base branch.
One warning so you are not surprised. The three weights cache test files are wired into the buck targets only. The cmake test target lists the executor test, the workspace manager test and the threadpool test, so nothing in the open source CI builds them today. A case added there is still worth having, but it will not turn CI red on a regression until that target is fixed.