Pre-check ConstantOfShape output size against input initializer before constant folding
A 152-byte ONNX model with a ConstantOfShape whose shape initializer encodes huge dims causes InferenceSession::Initialize() to materialize the full output tensor (287 MB in the PoC, up to PB-scale with fuzz-mutated dims) via the ConstantFolding optimizer. The existing pre-execution size cap in EstimateNodeOutputSizeInBytes relies on shape inference having populated output_def->Shape(), which is not guaranteed.
Description
-
onnxruntime/core/optimizer/constant_folding.cc- New
EstimateConstantOfShapeOutputSizeInBytes(node, graph): looks up the shape input viaGraph::GetConstantInitializer(it is constant by the time we reach this node), multiplies its int64 values withSafeInt<int64_t>(rejects negative dims, lets overflow propagate as an exception caught upstream), and multiplies by the element size derived from thevalueattribute's tensor type (defaulting to float per ONNX spec). EstimateNodeOutputSizeInBytesnow takesconst Graph&and dispatches to the new estimator forConstantOfShape, falling back to the generic shape-based path if the initializer can't be resolved.
- New
-
onnxruntime/test/optimizer/graph_transform_test.ccConstantFoldingConstantOfShapeUsesInputInitializerForSizeCheck: shape[100M]with int64value⇒ 800 MB derived size; withkOrtSessionOptionsConstantFoldingMaxOutputSizeInBytes=256MBthe node must remain unfolded, proving the size check fires from the initializer alone.ConstantFoldingConstantOfShapeBlockedWhenOutputShapeMissing: same model, but thepre_graph_checker(which runs afterGraph::Resolve()and before the transformer) callsClearShape()on the ConstantOfShape output NodeArg to simulate the documented attack where shape inference has not propagated the output shape. With the inferred shape stripped, the generic shape-based estimator returns -1, so only the newEstimateConstantOfShapeOutputSizeInBytespath can derive the 800 MB size and block folding — isolating regressions in the new estimator from the pre-existing shape-inference path.
Motivation and Context
The byte cap added in #28055 only triggers when shape inference has propagated the output shape; a crafted model can bypass it and force unbounded allocation during Initialize(). Deriving the size from the (necessarily constant) shape input makes the cap effective for the documented attack vector and tightens the same code path used by the configurable kOrtSessionOptionsConstantFoldingMaxOutputSizeInBytes setting — no new knob, no behavior change for legitimate models within the existing 1 GB default.