onnxruntime
bacfde99 - Validate constant tensor byte size in DML OnnxTensorWrapper

Commit
1 day ago
Validate constant tensor byte size in DML OnnxTensorWrapper OnnxTensorWrapper wraps an onnx::TensorProto and hands out a raw pointer via GetData() together with dimensions from GetShape(). Consumers size their reads from the shape and the declared element type, never from the actual size of the backing buffer. When a TensorProto declares dims and a data type whose implied byte size exceeds the bytes actually present, the consumer reads past the end of the heap allocation. For graph initializers this is already mitigated: ONNX shape inference calls ParseData, which rejects a raw_data field shorter than product(dims) * sizeof(element). That covers ops whose inference reads constant inputs (Slice, Pad, Tile, Expand, Reshape, Resize, Split). Attribute tensors are not covered by any of that. OpNodeInfoWrapper:: GetTensorAttribute builds an OnnxTensorWrapper directly from AttributeProto::t(). An attribute tensor is never a graph initializer, so Graph::ConvertInitializersIntoOrtValues and its embedded-data validation never see it, and ConstantOfShape's ONNX shape inference only reads the attribute's data_type - never its data. DmlOperatorConstantOfShape then checks elementCount == 1 from the dims and memcpy's a byte count derived from the declared data type out of GetData(). A `value` attribute of {INT64, dims=[1], raw_data = 1 byte} produces a 7-byte heap over-read whose contents become the GPU fill pattern, and therefore appear directly in the model output. Add VerifyTensorProtoFitsInBuffer, called at the end of the OnnxTensorWrapper constructor so it covers the external-data, raw_data, and typed-field branches alike. It computes the required byte size with GetSizeInBytesFromTensorProto<0> and throws E_INVALIDARG when the backing buffer is smaller. STRING is exempt because its size query returns elem_count * sizeof(std::string) rather than a serialized byte size, and the complex types are sized inline because that helper reports them as NOT_IMPLEMENTED even though ToMLTensorDataType maps them. Every other size-query failure - unrepresentable dims, byte-count overflow, an element type this EP does not admit - is treated as malformed rather than skipped, so the bounds check is total over the types that can reach a consumer. The check is a lower bound only. ValidateEmbeddedTensorProtoDataSizeAndShape was deliberately not reused because it also enforces exact equality and a 2 GiB cap; RegisterDynamicKernel re-packs arbitrarily large weights into raw_data protos that reach this constructor, so the cap risks regressing legitimate models. Tests: onnxruntime/test/providers/dml_onnx_tensor_wrapper_test.cc builds a ConstantOfShape model in memory with a truncated `value` attribute and asserts that session initialization now fails; with the constructor check removed that test fails, confirming the session previously initialized while over-reading. A positive control loads, initializes, and runs a well-formed equivalent and checks the output. ConstantFolding is disabled in both so the node reaches the DML EP instead of being folded on CPU at Level1. The constructor's typed-field branch is guarded too but has no test, because every malformed proto that would reach it is rejected earlier by the ONNX checker or by DmlOperatorConstantOfShape's own element-count assertion; the file documents this. Verified with onnxruntime_test_all (1896 tests, 1879 passed, 0 failed) and onnxruntime_provider_test --gtest_filter=*Dml* (14 passed) on a machine with a real DirectML device. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Author
Committer
Parents
Loading