Repository navigation
Fix external tensor copy validation, dense-to-sparse bounds, and ORT format initializer state - #33643
Open
m00dy (professor-moody) wants to merge 2 commits into
Conversation
…format initializer state ## Description This change hardens three initializer and `TensorProto` conversion paths in `onnxruntime/core/framework/tensorprotoutils.cc` and `onnxruntime/core/graph/graph.cc`: 1. **`TensorProtoToTensor` (`onnxruntime/core/framework/tensorprotoutils.cc:2054-2088`)**: Moves the `tensor_shape != tensor.Shape()`, element-type compatibility, and `Tensor::CalculateTensorStorageSize` capacity checks ahead of the `HasExternalData(tensor_proto)` branch so external-data tensors validate the destination `Tensor` shape, element type, and byte capacity before `MakeCpuTensorCopy(ext_tensor, tensor)` copies raw bytes into `tensor`. 2. **`DenseTensorToSparseTensorProto` (`onnxruntime/core/framework/tensorprotoutils.cc:2935-2965`)**: Calls `ValidateEmbeddedTensorProtoDataSizeAndShape(dense_proto)` when `!HasExternalData(dense_proto)`, rejects sub-byte tensor data types (`HasSubElems()`), and checks `dense_raw_data.size() == n_dense_elements * element_size` after `UnpackInitializerData` so truncated, oversized, or sub-byte packed tensor buffers cannot reach `SparsifyGeneric`. 3. **`Graph::LoadFromOrtFormat` in `onnxruntime/core/graph/graph.cc`**: When a repeated dense or sparse initializer name is encountered in an ORT-format flatbuffer and replaced by the latest initializer, erases the stale entry from `ortvalue_initializers_` (`ortvalue_initializers_.erase(initializer->name())` / `ortvalue_initializers_.erase(initializer.name())`) so `ortvalue_initializers_` stays consistent with `name_to_initial_tensor_`. ## Motivation and Context - In `TensorProtoToTensor`, embedded tensors validate `tensor_shape != tensor.Shape()` and element-type size compatibility before unpacking, whereas `HasExternalData(tensor_proto)` previously returned early after `MakeCpuTensorCopy(ext_tensor, tensor)` without running those checks (defense in depth for direct callers of `TensorProtoToTensor`). - In `DenseTensorToSparseTensorProto`, `SparsifyGeneric` iterates `n_dense_elements * element_size` bytes over `dense_raw_data.data()`. Without rejecting sub-byte types (`HasSubElems()`, where `ml_data->Size()` is 1 byte per packed element) and checking `dense_raw_data.size() == n_dense_elements * element_size`, a sub-byte or mismatched buffer over-reads `dense_raw_data`. - In `Graph::LoadFromOrtFormat`, if an earlier initializer with the same name populated `ortvalue_initializers_`, replacing only `name_to_initial_tensor_` left a stale `OrtValue` in `ortvalue_initializers_`. Added unit tests in `onnxruntime/test/framework/tensorutils_test.cc`: - `TensorProtoUtilsTest.ExternalTensorCopyPreservesMatchingDestination` - `TensorProtoUtilsTest.DenseSparseConversionPreservesTypedFloatValues` - `TensorProtoUtilsTest.RejectsMismatchedExternalTensorShapeAndOversizedDenseToSparseInput`
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Targeted regression coverage is still needed for capacity validation, packed-type rejection, and both initializer-cache replacement paths.
3 open issues
What changed in this PR
This PR hardens ONNX Runtime tensor conversion and initializer loading by validating copy bounds and keeping cached initializer values consistent.
Changes:
- Validates destination shape, element size, and capacity before external tensor copies.
- Rejects malformed or unsupported packed data before dense-to-sparse conversion.
- Clears stale cached values when ORT-format initializers are replaced and adds tensor utility tests.
| File | Description |
|---|---|
onnxruntime/test/framework/tensorutils_test.cc |
Adds successful conversion and invalid-input rejection tests. |
onnxruntime/core/graph/graph.cc |
Invalidates cached values for duplicate initializer replacements. |
onnxruntime/core/framework/tensorprotoutils.cc |
Adds copy-capacity and dense-to-sparse validation. |
Review effort: Balanced
Comment on lines
+2077
to
+2080
| size_t source_bytes; | ||
| ORT_RETURN_IF_ERROR(Tensor::CalculateTensorStorageSize(source_type, tensor_shape, 0, source_bytes)); | ||
| ORT_RETURN_IF(source_bytes > tensor.SizeInBytes(), | ||
| "TensorProto data exceeds destination tensor capacity"); |
Comment on lines
+2967
to
+2968
| ORT_RETURN_IF(ml_data->AsPrimitiveDataType()->HasSubElems(), | ||
| "Dense-to-sparse conversion does not support sub-byte tensor data types"); |
| ORT_RETURN_IF_ERROR(fbs::utils::LoadInitializerOrtFormat(*fbs_tensor, *initializer, load_options)); | ||
| auto p = name_to_initial_tensor_.emplace(initializer->name(), initializer); | ||
| if (!p.second) { | ||
| ORT_IGNORE_RETURN_VALUE(ortvalue_initializers_.erase(initializer->name())); |
…arse rejection, and ORT format duplicate initializers
Author
|
All three review comments are addressed in 81d4e45, which adds the requested regression tests. The production changes are unchanged.
Each new test was also run with only its guard removed:
Full unit test run (Linux x86_64, Release), no filter: 2537 tests, 2511 passed, 26 skipped, 0 failed. One whitespace-only alignment fix in an existing test line from the first commit ( |
This branch has not been deployed
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.

Description
This change hardens three initializer and
TensorProtoconversion paths inonnxruntime/core/framework/tensorprotoutils.ccandonnxruntime/core/graph/graph.cc:TensorProtoToTensor(onnxruntime/core/framework/tensorprotoutils.cc:2054-2088): Moves thetensor_shape != tensor.Shape(), element-type compatibility, andTensor::CalculateTensorStorageSizecapacity checks ahead of theHasExternalData(tensor_proto)branch so external-data tensors validate the destinationTensorshape, element type, and byte capacity beforeMakeCpuTensorCopy(ext_tensor, tensor)copies raw bytes intotensor.DenseTensorToSparseTensorProto(onnxruntime/core/framework/tensorprotoutils.cc:2935-2965): CallsValidateEmbeddedTensorProtoDataSizeAndShape(dense_proto)when!HasExternalData(dense_proto), rejects sub-byte tensor data types (HasSubElems()), and checksdense_raw_data.size() == n_dense_elements * element_sizeafterUnpackInitializerDataso truncated, oversized, or sub-byte packed tensor buffers cannot reachSparsifyGeneric.Graph::LoadFromOrtFormatinonnxruntime/core/graph/graph.cc: When a repeated dense or sparse initializer name is encountered in an ORT-format flatbuffer and replaced by the latest initializer, erases the stale entry fromortvalue_initializers_(ortvalue_initializers_.erase(initializer->name())/ortvalue_initializers_.erase(initializer.name())) soortvalue_initializers_stays consistent withname_to_initial_tensor_.Motivation and Context
TensorProtoToTensor, embedded tensors validatetensor_shape != tensor.Shape()and element-type size compatibility before unpacking, whereasHasExternalData(tensor_proto)previously returned early afterMakeCpuTensorCopy(ext_tensor, tensor)without running those checks (defense in depth for direct callers ofTensorProtoToTensor).DenseTensorToSparseTensorProto,SparsifyGenericiteratesn_dense_elements * element_sizebytes overdense_raw_data.data(). Without rejecting sub-byte types (HasSubElems(), whereml_data->Size()is 1 byte per packed element) and checkingdense_raw_data.size() == n_dense_elements * element_size, a sub-byte or mismatched buffer over-readsdense_raw_data.Graph::LoadFromOrtFormat, if an earlier initializer with the same name populatedortvalue_initializers_, replacing onlyname_to_initial_tensor_left a staleOrtValueinortvalue_initializers_.Added unit tests in
onnxruntime/test/framework/tensorutils_test.cc:TensorProtoUtilsTest.ExternalTensorCopyPreservesMatchingDestinationTensorProtoUtilsTest.ExternalTensorCopyRejectsInsufficientDestinationCapacity(externalUINT8source into aUINT4destination of the same shape: equal element sizes, 4 versus 2 storage bytes; the destination is left unchanged)TensorProtoUtilsTest.ExternalTensorCopyRejectsWiderSourceElementType(externalFLOATsource into aUINT8destination of the same shape)TensorProtoUtilsTest.DenseSparseConversionPreservesTypedFloatValuesTensorProtoUtilsTest.RejectsMismatchedExternalTensorShapeAndOversizedDenseToSparseInputTensorProtoUtilsTest.DenseToSparseRejectsPackedSubByteTypes(well-formed packedINT4,UINT4,INT2andUINT2inputs, including one-element tensors whose one-byte buffer also passes the later size check)Added unit tests in
onnxruntime/test/framework/ort_model_only_test.cc(ORT format loaded withkOrtSessionOptionsConfigUseORTModelBytesForInitializers, socan_use_flatbuffer_for_initializersis enabled):OrtModelTest.OrtFormatLargeInitializerIsCachedWhenUsingBytesForInitializers(control: a 160-byte initializer is cached as anOrtValue)OrtModelTest.OrtFormatDuplicateDenseInitializerReplacesCachedValue(replacement by a 16-byte dense initializer)OrtModelTest.OrtFormatDuplicateSparseInitializerReplacesCachedValue(replacement by a sparse initializer with different values)In each replacement test
GetInitializedTensorreturns the replacement values andGetOrtValueInitializerreports no cached value.Each new rejection or replacement test fails when only its corresponding production change is reverted and passes with this change (Linux x86_64 Release build, full unfiltered unit test run).
Note: open PR #33659 also adds tests to
onnxruntime/test/framework/ort_model_only_test.cc; the two changes touch disjoint regions of that file (this change at lines 476-481 of the base file, #33659 at lines 443-448) and apply cleanly in either order.