fix: guard TensorProto data size against dims in createElmAttrFromArray - #3616
fix: guard TensorProto data size against dims in createElmAttrFromArray#3616jonathanhuang-bot wants to merge 1 commit into
Conversation
The destination buffer in fromRawBytes() is sized from the tensor's declared dims (getNumElements()*bytewidth), but std::transform in createElmAttrFromArray wrote array.size() elements without checking that the two match. A crafted .onnx with more data bytes than dims implied caused a heap buffer overflow with fully attacker-controlled length and bytes. Hunk 1 (FrontendDialectHelper.cpp): add a size check in createElmAttrFromArray before calling fromArray. This closes the raw_data and typed proto field (int32_data/float_data/etc.) paths, which both funnel through this chokepoint unconditionally. Hunk 2 (ElementsAttrBuilder.cpp): add a size check in fromMemoryBuffer. On little-endian hosts (and for single-byte dtypes on any host), the EXTERNAL data-location path calls fromMemoryBuffer directly, bypassing createElmAttrFromArray entirely. This second guard closes that remaining path. Both checks use llvm::report_fatal_error so they fire in Release builds (unlike assert which is a no-op under NDEBUG). Signed-off-by: Jonathan Huang <jonathanhuang@ibm.com>
|
Can one of the admins verify this patch? |
Note on deviation from the fable candidate patch (bug_06)The fable candidate patch bug_06/patch.diff adds a size guard to createElmAttrFromArray in FrontendDialectHelper.cpp. That guard is included here unchanged (Hunk 1). However, bug_06 alone is not a complete fix — a third attacker-reachable code path bypasses createElmAttrFromArray entirely and was left unguarded. This PR adds a second guard (Hunk 2) to close it. Why bug_06 is incomplete:A TensorProto can carry its element data in three ways. Two of them — raw_data and typed proto fields (float_data, int32_data, etc.) — always funnel through createElmAttrFromArray, so bug_06's guard covers them. The third — data_location == EXTERNAL — routes through createElementsAttrFromMemoryBuffer_LE, which contains a compile-time branch: shouldSwapLEBytes is sizeof(T) > 1 && native_endianness != little. It is false — sending EXTERNAL data straight to fromMemoryBuffer with no size check — in two cases:
What Hunk 2 adds:A byte-level size check in ElementsAttrBuilder::fromMemoryBuffer — comparing membuf->getBufferSize() against type.getNumElements() * bytewidthOfBType(btype) — that fires before the buffer is wrapped in a DisposableElementsAttr and handed to downstream compilation passes. This closes the EXTERNAL path for all dtypes on all hosts. The check is compile-time-only (model import), with zero inference-path cost. |
The destination buffer in fromRawBytes() is sized from the tensor's declared dims (getNumElements()*bytewidth), but std::transform in createElmAttrFromArray wrote array.size() elements without checking that the two match. A crafted .onnx with more data bytes than dims implied caused a heap buffer overflow with fully attacker-controlled length and bytes.
Hunk 1 (FrontendDialectHelper.cpp): add a size check in createElmAttrFromArray before calling fromArray. This closes the raw_data and typed proto field (int32_data/float_data/etc.) paths, which both funnel through this chokepoint unconditionally.
Hunk 2 (ElementsAttrBuilder.cpp): add a size check in fromMemoryBuffer. On little-endian hosts (and for single-byte dtypes on any host), the EXTERNAL data-location path calls fromMemoryBuffer directly, bypassing createElmAttrFromArray entirely. This second guard closes that remaining path.
Both checks use llvm::report_fatal_error so they fire in Release builds (unlike assert which is a no-op under NDEBUG).
Analysis file for this bug: disposition-f005-analysis.md