From c2c67e314d381358a5d9a54feee75f6a9c9dd0fe Mon Sep 17 00:00:00 2001 From: "git config --global user.name vthaniel" Date: Tue, 23 Jun 2026 10:10:10 +0530 Subject: [PATCH] Fix: OpenVINO EP DeserializeImpl allows unbounded uint64 allocation --- .../core/providers/openvino/ov_bin_manager.cc | 21 +++ .../openvino/openvino_ep_context_test.cc | 132 ++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/onnxruntime/core/providers/openvino/ov_bin_manager.cc b/onnxruntime/core/providers/openvino/ov_bin_manager.cc index f1897ecbfa1fd..9709366f5b3d5 100644 --- a/onnxruntime/core/providers/openvino/ov_bin_manager.cc +++ b/onnxruntime/core/providers/openvino/ov_bin_manager.cc @@ -347,6 +347,22 @@ void BinManager::DeserializeImpl(std::istream& stream, const std::shared_ptr(stream.tellg()); + + // Overflow-safe check that [offset, offset + size) fits within the stream. + auto range_within_stream = [stream_size](uint64_t offset, uint64_t size) { + return offset <= stream_size && size <= stream_size - offset; + }; + + // Validate the BSON region lies fully within the file before allocating for it. + ORT_ENFORCE(range_within_stream(header.bson_start_offset, header.bson_size), + "Error: BSON region out of bounds. Offset: ", header.bson_start_offset, + " Size: ", header.bson_size, " File size: ", stream_size); + // Seek to BSON metadata and read it stream.seekg(header.bson_start_offset); ORT_ENFORCE(stream.good(), "Error: Failed to seek to BSON metadata."); @@ -417,6 +433,11 @@ void BinManager::DeserializeImpl(std::istream& stream, const std::shared_ptr(&header), sizeof(header)); +} + +// Serializes a synthetic model containing a single embedded-mode EPContext node +// whose "source" is the OpenVINO EP and whose "ep_cache_context" carries the +// supplied OVEP_BIN payload. Loading this model drives BinManager::Deserialize. +std::string MakeEmbeddedEPContextModel(const std::string& ep_cache_context) { + ModelProto model; + model.set_ir_version(ONNX_NAMESPACE::Version::IR_VERSION); + auto* opset = model.add_opset_import(); + opset->set_domain(""); + opset->set_version(13); + auto* ms_opset = model.add_opset_import(); + ms_opset->set_domain(kMSDomain); + ms_opset->set_version(1); + + auto* graph = model.mutable_graph(); + graph->set_name("OVEP_BinDeserialize_Test"); + + auto* input = graph->add_input(); + input->set_name("input"); + auto* input_type = input->mutable_type()->mutable_tensor_type(); + input_type->set_elem_type(TensorProto_DataType_FLOAT); + input_type->mutable_shape()->add_dim()->set_dim_value(1); + input_type->mutable_shape()->add_dim()->set_dim_value(3); + + auto* output = graph->add_output(); + output->set_name("output"); + auto* output_type = output->mutable_type()->mutable_tensor_type(); + output_type->set_elem_type(TensorProto_DataType_FLOAT); + output_type->mutable_shape()->add_dim()->set_dim_value(1); + output_type->mutable_shape()->add_dim()->set_dim_value(3); + + auto* node = graph->add_node(); + node->set_op_type("EPContext"); + node->set_domain(kMSDomain); + node->set_name("ep_context_node"); + node->add_input("input"); + node->add_output("output"); + + auto* attr_embed = node->add_attribute(); + attr_embed->set_name("embed_mode"); + attr_embed->set_type(AttributeProto_AttributeType_INT); + attr_embed->set_i(1); + + auto* attr_main = node->add_attribute(); + attr_main->set_name("main_context"); + attr_main->set_type(AttributeProto_AttributeType_INT); + attr_main->set_i(1); + + auto* attr_cache = node->add_attribute(); + attr_cache->set_name("ep_cache_context"); + attr_cache->set_type(AttributeProto_AttributeType_STRING); + attr_cache->set_s(ep_cache_context); + + auto* attr_source = node->add_attribute(); + attr_source->set_name("source"); + attr_source->set_type(AttributeProto_AttributeType_STRING); + attr_source->set_s("OpenVINOExecutionProvider"); + + auto* attr_partition = node->add_attribute(); + attr_partition->set_name("partition_name"); + attr_partition->set_type(AttributeProto_AttributeType_STRING); + attr_partition->set_s("OVEP_BinDeserialize_Test"); + + std::string model_data; + model.SerializeToString(&model_data); + return model_data; +} + +} // namespace + +// Regression test for the unbounded-allocation hardening in +// BinManager::DeserializeImpl. A crafted EP-context blob advertises a BSON +// region whose size dwarfs the actual payload. Deserialization must reject it +// with a bounded, descriptive error instead of attempting a huge allocation. +TEST_F(OVEPEPContextTests, OVEPBinDeserializeRejectsOversizedBson) { + Ort::SessionOptions session_options; + std::unordered_map ov_options; + // Empty options -> use the device the OVEP build targets (mirrors other tests + // in this file). Skip the test entirely if no OpenVINO device is available. + try { + session_options.AppendExecutionProvider_OpenVINO_V2(ov_options); + } catch (const Ort::Exception&) { + GTEST_SKIP() << "OpenVINO device not available on this machine"; + } + + auto& logging_manager = DefaultLoggingManager(); + logging_manager.SetDefaultLoggerSeverity(logging::Severity::kERROR); + + // bson_start_offset (40) + bson_size (1 TiB) is far beyond the ~40 byte blob. + const std::string malicious_blob = MakeBinBlobWithOversizedBson(uint64_t{1} << 40); + const std::string model_data = MakeEmbeddedEPContextModel(malicious_blob); + + try { + Ort::Session session(*ort_env, model_data.data(), model_data.size(), session_options); + FAIL() << "Expected deserialization of an oversized-BSON blob to throw."; + } catch (const Ort::Exception& excpt) { + // The new bounds check produces "BSON region out of bounds ...", which + // BinManager::Deserialize wraps with a "Could not deserialize" message. + // Asserting on "out of bounds" confirms the bounds check fired (rather than + // a std::bad_alloc/length_error from an unbounded allocation attempt). + ASSERT_THAT(excpt.what(), testing::HasSubstr("out of bounds")); + } +} + } // namespace test } // namespace onnxruntime