YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
OpenVINO IR rt_info <user_data> deserialization: unbounded recursion β native stack overflow (SIGSEGV DoS) in read_model
Summary
ov::Core().read_model() can be crashed (SIGSEGV, process-fatal denial of service) by loading a
crafted, otherwise-valid OpenVINO IR (.xml) model. The IR deserializer recurses once per nesting
level of <user_data name="..."> elements inside any layer's <rt_info>, with no recursion-depth
limit. A deeply nested <user_data> chain exhausts the calling thread's native C++ stack and
crashes the process before any graph/shape validation runs.
- Target: OpenVINO β IR (v11) XML front-end / model deserializer
- Affected component:
src/core/xml_util/src/xml_deserialize_util.cpp, functionset_custom_rt_info() - Verified on:
openvino 2026.2.1(pip wheel, CPU), version string2026.2.1-21919-ede283a88e3-releases/2026/2, Linux x86-64, default 8 MB thread stack - Impact: Denial of service (native stack-overflow SIGSEGV) triggered purely by loading an attacker-supplied model file. No ASan build required to observe the crash.
- Attack surface: Any application that calls
read_model/compile_modelon an untrusted IR file (model zoos, model-conversion services, CI that loads third-party models, etc.).
Root cause
set_custom_rt_info() iterates a node's <rt_info> children. For each child named user_data that
has a name attribute but no value attribute, it recurses into that element's own children to
build a nested AnyMap:
// src/core/xml_util/src/xml_deserialize_util.cpp
void set_custom_rt_info(const pugi::xml_node& rt_attrs, ov::AnyMap& rt_info, bool prefix_needed = true) {
constexpr std::string_view rt_info_user_data_tag{"user_data"};
std::string custom_name, custom_value;
for (const auto& item : rt_attrs) {
if (std::strcmp(item.name(), rt_info_user_data_tag.data()) == 0) {
if (getStrAttribute(item, "name", custom_name)) {
const auto name = std::string{prefix_needed ? rt_info_user_data_tag : ""} + custom_name;
if (getStrAttribute(item, "value", custom_value)) {
rt_info.emplace(name, custom_value); // non-recursive branch (has value=)
} else {
rt_info.erase(name);
if (auto map_elem = rt_info.emplace(name, ov::AnyMap{}); map_elem.second) {
auto& nested_map = map_elem.first->second.as<ov::AnyMap>();
set_custom_rt_info(item, nested_map, false); // <-- UNBOUNDED RECURSION (line ~147)
}
}
}
}
}
}
Every nesting level of <user_data name="a"> ... </user_data> (with no value attribute) drives one
additional native C++ stack frame during deserialization, called from the layer-rt_info parse path
(set_custom_rt_info(rt_attrs, rt_info);). There is no depth cap, so a sufficiently deep chain
exhausts the thread stack and the process dies with SIGSEGV.
This path is distinct from the previously reported If/Loop nested-subgraph parse_function recursion:
- Different function (
set_custom_rt_info, notparse_function). - Different subsystem (metadata/
rt_infoparsing, not control-flow subgraph parsing). - No control-flow ops required β any ordinary node's
<rt_info>suffices (here a plainReLU).
Proof of concept
Generator: gen_rtinfo_recursion.py emits a minimal valid IR (Parameter β ReLU β Result) whose
ReLU layer carries an <rt_info> containing N nested <user_data name="a"> elements (each with no
value attribute, so the recursive branch is taken every level).
python3 gen_rtinfo_recursion.py 20000 poc.xml # writes poc.xml + empty poc.bin
python -c "import openvino as ov; ov.Core().read_model('poc.xml')" # SIGSEGV
Self-contained repro files included: selfpoc.xml / selfpoc.bin (depth 20000, crashes),
selfctrl.xml / selfctrl.bin (depth 1000, loads OK). Also ud_20000.xml, ud_1000.xml,
ud_50000.xml, and the two negative controls ctrl_zzz.xml, ctrl_udval.xml.
Captured evidence (verbatim)
Primary repro, faulthandler shows the crash inside read_model:
$ ./venv/bin/python -X faulthandler -c "import openvino as ov; print('OV',ov.__version__); ov.Core().read_model('ud_20000.xml'); print('NOCRASH')"
OV 2026.2.1-21919-ede283a88e3-releases/2026/2
Fatal Python error: Segmentation fault
Current thread 0x00007f885a7d2200 (most recent call first):
File ".../openvino/_ov_api.py", line 601 in read_model
File "<string>", line 1 in <module>
Extension modules: numpy._core._multiarray_umath, numpy.linalg._umath_linalg (total: 2)
RC=139
Depth threshold (default 8 MB stack)
| depth | result |
|---|---|
| 1000 / 5000 / 10000 | LOADED OK (rc=0) |
| 20000 / 50000 / 200000 / 500000 | SIGSEGV (rc=139) |
Reproducibility
ud_20000.xml crashes 3/3 fresh processes: run1 rc=139 / run2 rc=139 / run3 rc=139.
Negative control 1 β isolates pugixml (not deep-XML parsing)
ctrl_zzz.xml = same depth (50000) but nested non-user_data tags (<zzz>...):
β LOADED_OK (rc=0). Proves the crash is not generic deep-XML/pugixml parsing but the user_data
recursion specifically.
Negative control 2 β isolates the recursive branch
ctrl_udval.xml = 50000 nested <user_data name="a" value="v"> (with value attribute, which
takes the non-recursive emplace branch): β LOADED_OK (rc=0). Proves it is the value-less recursive
branch, not user_data nesting per se.
Stack-size scaling β definitive stack-overflow signature
ud_50000.xml:
- default 8 MB stack β SIGSEGV (rc=139)
ulimit -s 65536(64 MB stack) β LOADED_OK (rc=0)
The crash threshold moves with available stack β call-stack exhaustion, not heap/data corruption.
Dedup / prior-art note
- Distinct from OpenVINO's If/Loop nested-subgraph
parse_functionunbounded-recursion issue (different function, different subsystem, and this one needs no control-flow ops). - No public CVE was found for
set_custom_rt_info/rt_infouser_datarecursion at time of writing. - The generic class (missing recursion-depth limit in a recursive-descent model parser) is a known
memory-safety pattern; this report identifies a specific, independently reachable instance in the
rt_infometadata path.
Suggested fix
Add an explicit recursion-depth limit (or convert to an explicit work-stack with a cap) in
set_custom_rt_info(), rejecting the model with a clear error once a reasonable nesting depth is
exceeded.