Affected Version: onnxruntime 1.23.2 (latest) and all prior versions with Resize op (opset >= 18)
Root Cause
In upsamplebase.h, ParseScalesData() performs a memcpy without verifying the destination buffer has sufficient capacity:
cpp
1// upsamplebase.h line 543-5512ParseScalesData(const Tensor* scale, InlinedVector<float>& scales,int64_t rank)const{3constauto* scale_data = scale->Data<float>();4int64_t scales_size = scale->Shape().Size();// attacker-controlled from model5ORT_RETURN_IF_NOT(scales_size >0,"...");// only checks > 06if(scales.empty()){// FALSE at runtime!7 scales.resize(onnxruntime::narrow<size_t>(scales_size));8}9memcpy(scales.data(), scale_data,SafeInt<size_t>(scales_size)*sizeof(float));// OVERFLOW
When called from Compute() at runtime (upsample.cc line 1390), scales is pre-initialized with input_dims.size() elements (e.g., 4 for NCHW input), making it non-empty. The resize() is skipped, but memcpy writes scales_size elements (attacker-controlled from model) into the undersized buffer.
When the model uses opset >= 18 and input X has no shape annotation (dynamic shape), rank = -1 at construction time. The caching condition fails, so scales_cached_ = false. At runtime, the vulnerable ParseScalesData path is taken.
Attack Vector
Attacker crafts a .onnx model with a Resize op (opset 19)
Input X has no shape annotation (common in dynamic-shape models)
Scales tensor is embedded as initializer with 256+ elements (vs expected 4 for NCHW)
Victim loads model with onnxruntime.InferenceSession()
In ParseScalesData(), always resize the destination buffer to match scales_size before memcpy:
cpp
1// Fix: always ensure buffer is large enough, regardless of empty() state2if(scales.size()<static_cast<size_t>(scales_size)){3 scales.resize(onnxruntime::narrow<size_t>(scales_size));4}5memcpy(scales.data(), scale_data,SafeInt<size_t>(scales_size)*sizeof(float));
Or alternatively, validate scales_size against current buffer size: