Skip to content
1 change: 1 addition & 0 deletions onnxruntime/core/flatbuffers/flatbuffers_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ Status LoadValueInfoOrtFormat(const fbs::ValueInfo& fbs_value_info,
Status LoadOpsetImportOrtFormat(const flatbuffers::Vector<flatbuffers::Offset<fbs::OperatorSetId>>* fbs_op_set_ids,
std::unordered_map<std::string, int>& domain_to_version) {
ORT_RETURN_IF(nullptr == fbs_op_set_ids, "Model must have opset imports. Invalid ORT format model.");
ORT_RETURN_IF_ERROR(ValidateRequiredTableOffsets(fbs_op_set_ids, "opset import"));

domain_to_version.clear();
domain_to_version.reserve(fbs_op_set_ids->size());
Expand Down
18 changes: 18 additions & 0 deletions onnxruntime/core/flatbuffers/flatbuffers_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ onnxruntime::common::Status LoadOpsetImportOrtFormat(
const flatbuffers::Vector<flatbuffers::Offset<fbs::OperatorSetId>>* fbs_op_set_ids,
std::unordered_map<std::string, int>& domain_to_version);

template <typename T>
inline onnxruntime::common::Status ValidateRequiredTableOffsets(
const flatbuffers::Vector<flatbuffers::Offset<T>>* fbs_entries,
const char* entry_description) {
if (fbs_entries == nullptr) {
return onnxruntime::common::Status::OK();
}

const auto* raw_offsets = reinterpret_cast<const uint8_t*>(fbs_entries->Data());
for (flatbuffers::uoffset_t i = 0; i < fbs_entries->size(); ++i) {
const auto entry_offset =
flatbuffers::ReadScalar<flatbuffers::uoffset_t>(raw_offsets + i * sizeof(flatbuffers::uoffset_t));
ORT_RETURN_IF(entry_offset == 0, "Null ", entry_description, " entry. Invalid ORT format model.");
}

return onnxruntime::common::Status::OK();
}

Comment thread
tianleiwu marked this conversation as resolved.
// check if filename ends in .ort
bool IsOrtFormatModel(const PathString& filename);

Expand Down
93 changes: 84 additions & 9 deletions onnxruntime/core/graph/graph.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <cassert>
#include <fstream>
#include <iostream>
#include <limits>
#include <numeric>
#include <queue>
#include <stack>
Expand Down Expand Up @@ -896,7 +897,16 @@
if (fbs_edges) {
for (const auto* fbs_edge : *fbs_edges) {
ORT_RETURN_IF(nullptr == fbs_edge, "Node::LoadEdgesFromOrtFormat, edge is missing for ", dst_name);
edge_set.emplace(*graph.GetNode(fbs_edge->node_index()), fbs_edge->src_arg_index(), fbs_edge->dst_arg_index());
const auto edge_node_index = fbs_edge->node_index();
const size_t node_slot_count = static_cast<size_t>(graph.MaxNodeIndex());
ORT_RETURN_IF(static_cast<size_t>(edge_node_index) >= node_slot_count,
"Node::LoadEdgesFromOrtFormat, ", dst_name, " has out-of-range node index ",
edge_node_index, ". Invalid ORT format model.");
const auto* edge_node = graph.GetNode(edge_node_index);
ORT_RETURN_IF(edge_node == nullptr,
"Node::LoadEdgesFromOrtFormat, ", dst_name, " references missing node ",
edge_node_index, ". Invalid ORT format model.");
edge_set.emplace(*edge_node, fbs_edge->src_arg_index(), fbs_edge->dst_arg_index());
}
}
return Status::OK();
Expand Down Expand Up @@ -6640,8 +6650,10 @@

// Initializers
auto fbs_initializers = fbs_graph.initializers();
ORT_RETURN_IF_ERROR(fbs::utils::ValidateRequiredTableOffsets(fbs_initializers, "initializer"));
#if !defined(DISABLE_SPARSE_TENSORS)
auto fbs_sparse_initializers = fbs_graph.sparse_initializers();
ORT_RETURN_IF_ERROR(fbs::utils::ValidateRequiredTableOffsets(fbs_sparse_initializers, "sparse initializer"));
flatbuffers::uoffset_t map_size = (fbs_initializers != nullptr ? fbs_initializers->size() : 0U) +
(fbs_sparse_initializers != nullptr ? fbs_sparse_initializers->size() : 0U);
#else
Expand Down Expand Up @@ -6711,23 +6723,80 @@
// NodeArgs
auto fbs_node_args = fbs_graph.node_args();
if (fbs_node_args) {
ORT_RETURN_IF_ERROR(fbs::utils::ValidateRequiredTableOffsets(fbs_node_args, "node arg"));
node_args_.reserve(fbs_node_args->size());
for (const auto* fbs_value_info : *fbs_node_args) {
ORT_RETURN_IF(nullptr == fbs_value_info, "NodeArg is missing. Invalid ORT format model.");
NodeArgInfo node_arg_info;
ORT_RETURN_IF_ERROR(fbs::utils::LoadValueInfoOrtFormat(*fbs_value_info, node_arg_info));
const auto* name = fbs_value_info->name();
ORT_RETURN_IF(name == nullptr, "NodeArg name is missing. Invalid ORT format model.");
node_args_[name->str()] = std::make_unique<NodeArg>(std::move(node_arg_info));
const auto inserted = node_args_.emplace(name->str(), std::make_unique<NodeArg>(std::move(node_arg_info)));
ORT_RETURN_IF(!inserted.second, "Duplicate NodeArg name '", name->str(), "'. Invalid ORT format model.");
}
}

// Nodes
//
// Since we access a node using its index, we need to have nodes_ with size max_node_index to avoid
// out of bounds access.
nodes_.resize(fbs_graph.max_node_index());
// Since we access a node using its index, we need to have nodes_ with a size that covers all
// referenced indices. We compute the required slot count from actual node and edge data rather
// than trusting the serialized max_node_index field.
auto* fbs_nodes = fbs_graph.nodes();
ORT_RETURN_IF_ERROR(fbs::utils::ValidateRequiredTableOffsets(fbs_nodes, "node"));
auto* fbs_node_edges = fbs_graph.node_edges();
ORT_RETURN_IF_ERROR(fbs::utils::ValidateRequiredTableOffsets(fbs_node_edges, "node edge"));

uint32_t max_referenced_node_index = 0;
bool has_referenced_node_index = false;
const auto update_max_referenced_node_index = [&max_referenced_node_index,
&has_referenced_node_index](uint32_t node_index) {
max_referenced_node_index = has_referenced_node_index ? std::max(max_referenced_node_index, node_index)
: node_index;
has_referenced_node_index = true;
};

if (fbs_nodes != nullptr) {
for (const auto* fbs_node : *fbs_nodes) {
ORT_RETURN_IF(nullptr == fbs_node, "Node is missing. Invalid ORT format model.");
update_max_referenced_node_index(fbs_node->index());
}
}

if (fbs_node_edges != nullptr) {
for (const auto* fbs_node_edge : *fbs_node_edges) {
ORT_RETURN_IF(nullptr == fbs_node_edge, "NodeEdge is missing. Invalid ORT format model.");
update_max_referenced_node_index(fbs_node_edge->node_index());
}
}

const uint64_t required_node_slot_count_64 = has_referenced_node_index
? static_cast<uint64_t>(max_referenced_node_index) + 1U
: 0U;
ORT_RETURN_IF(required_node_slot_count_64 > std::numeric_limits<size_t>::max(),
"Node index ", max_referenced_node_index,
" is out of range. Invalid ORT format model.");
const size_t required_node_slot_count = static_cast<size_t>(required_node_slot_count_64);

// Sanity bound: reject buffers where a crafted node index would cause excessive allocation.
// ORT preserves original node indices after graph optimizations, so legitimate models can have
// sparse node slots. Allow that sparsity, but keep an absolute cap far above expected real model sizes.
const size_t total_entries = (fbs_nodes != nullptr ? fbs_nodes->size() : 0U) +
(fbs_node_edges != nullptr ? fbs_node_edges->size() : 0U);
constexpr size_t kMinSlotCap = 1024;
constexpr size_t kMaxNodeSlotCount = 1000000; // ~8 MB of unique_ptr<Node> slots on 64-bit
constexpr size_t kSparseNodeSlotMultiplier = 64;
const size_t sparse_slot_cap = total_entries > kMaxNodeSlotCount / kSparseNodeSlotMultiplier
? kMaxNodeSlotCount
: total_entries * kSparseNodeSlotMultiplier;
const size_t slot_cap = std::min(kMaxNodeSlotCount, std::max(kMinSlotCap, sparse_slot_cap));
ORT_RETURN_IF(required_node_slot_count > slot_cap,
"Node index ", required_node_slot_count - 1,
" is unreasonably large relative to the number of entries (",
total_entries, "). Invalid ORT format model.");

ORT_RETURN_IF(fbs_graph.max_node_index() < required_node_slot_count,
"Serialized max node index is smaller than the required node slot count. Invalid ORT format model.");
nodes_.resize(required_node_slot_count);
Comment thread
tianleiwu marked this conversation as resolved.

// It is possible to have no nodes in the model. Most likely scenario is the subgraph of an If Node
// where the subgraph returns a Constant node. The Constant node will be lifted to an initializer by ORT
Expand All @@ -6737,18 +6806,22 @@
ORT_RETURN_IF(nullptr == fbs_node, "Node is missing. Invalid ORT format model.");
std::unique_ptr<Node> node;
ORT_RETURN_IF_ERROR(Node::LoadFromOrtFormat(*fbs_node, *this, load_options, logger_, node));
ORT_RETURN_IF(node->Index() >= fbs_graph.max_node_index(), "Node index is out of range");
ORT_RETURN_IF(node->Index() >= nodes_.size(), "Node index is out of range");
ORT_RETURN_IF(nodes_[node->Index()] != nullptr,
"Duplicate node index ", node->Index(), ". Invalid ORT format model.");
nodes_[node->Index()] = std::move(node);
++num_of_nodes_;
}
}

// NodeEdges
auto* fbs_node_edges = fbs_graph.node_edges();
if (fbs_node_edges != nullptr) {
for (const auto* fbs_node_edge : *fbs_node_edges) {
ORT_RETURN_IF(nullptr == fbs_node_edge, "NodeEdge is missing. Invalid ORT format model.");
ORT_RETURN_IF(fbs_node_edge->node_index() >= fbs_graph.max_node_index(), "Node index is out of range");
ORT_RETURN_IF(fbs_node_edge->node_index() >= nodes_.size(), "Node index is out of range");

Check warning on line 6821 in onnxruntime/core/graph/graph.cc

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 Add #include <utility> for move [build/include_what_you_use] [4] Raw Output: onnxruntime/core/graph/graph.cc:6821: Add #include <utility> for move [build/include_what_you_use] [4]
ORT_RETURN_IF(nodes_[fbs_node_edge->node_index()] == nullptr,
"NodeEdge references missing node ", fbs_node_edge->node_index(),
". Invalid ORT format model.");
ORT_RETURN_IF_ERROR(nodes_[fbs_node_edge->node_index()]->LoadEdgesFromOrtFormat(*fbs_node_edge, *this));
Comment thread
tianleiwu marked this conversation as resolved.
}
}
Expand All @@ -6760,7 +6833,9 @@
node_args.reserve(fbs_node_args->size());
for (const auto* fbs_node_arg_name : *fbs_node_args) {
ORT_RETURN_IF(nullptr == fbs_node_arg_name, "NodeArg Name is missing. Invalid ORT format model.");
gsl::not_null<NodeArg*> node_arg = GetNodeArg(fbs_node_arg_name->str());
auto* node_arg = GetNodeArg(fbs_node_arg_name->str());
ORT_RETURN_IF(node_arg == nullptr, "Graph references unknown NodeArg '", fbs_node_arg_name->str(),
"'. Invalid ORT format model.");
node_args.push_back(node_arg);
}
}
Expand Down
10 changes: 10 additions & 0 deletions onnxruntime/core/graph/graph_flatbuffers_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,16 @@ Status LoadInitializerOrtFormat(const fbs::Tensor& fbs_tensor, TensorProto& init
} else {
const auto* fbs_raw_data = fbs_tensor.raw_data();
if (fbs_raw_data) {
size_t expected_num_bytes = 0;
ORT_RETURN_IF_ERROR(GetSizeInBytesFromFbsTensor(fbs_tensor, expected_num_bytes));
const auto* fbs_name = fbs_tensor.name();
const char* tensor_name = fbs_name ? fbs_name->c_str() : "<unnamed>";
ORT_RETURN_IF(
fbs_raw_data->size() != expected_num_bytes,
Comment thread
tianleiwu marked this conversation as resolved.
"Initializer raw data size mismatch for tensor '", tensor_name,
"'. Expected ", expected_num_bytes, " bytes but found ", fbs_raw_data->size(),
". Invalid ORT format model.");

if (load_options.can_use_flatbuffer_for_initializers && fbs_raw_data->size() > 127) {
static_assert(sizeof(void*) <= sizeof(ExternalDataInfo::OFFSET_TYPE));
const void* data_offset = fbs_raw_data->Data();
Expand Down
1 change: 1 addition & 0 deletions onnxruntime/core/graph/model.cc
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,7 @@ common::Status Model::LoadFromOrtFormat(const fbs::Model& fbs_model,

// Load the model metadata
if (const auto* fbs_metadata_props = fbs_model.metadata_props()) {
ORT_RETURN_IF_ERROR(fbs::utils::ValidateRequiredTableOffsets(fbs_metadata_props, "metadata property"));
model->model_metadata_.reserve(fbs_metadata_props->size());
for (const auto* prop : *fbs_metadata_props) {
ORT_RETURN_IF(nullptr == prop, "Null entry in metadata_props. Invalid ORT format model.");
Expand Down
Loading
Loading