You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

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, function set_custom_rt_info()
  • Verified on: openvino 2026.2.1 (pip wheel, CPU), version string 2026.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_model on 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, not parse_function).
  • Different subsystem (metadata/rt_info parsing, not control-flow subgraph parsing).
  • No control-flow ops required β€” any ordinary node's <rt_info> suffices (here a plain ReLU).

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_function unbounded-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_info user_data recursion 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_info metadata 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.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support