YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

MFV β€” OpenVINO IR model file (.xml + .bin): offset+size overflow β†’ OOB read + shape_size overflow

Target format (huntr): OpenVINO ($1500 tier, "Model File Formats") Affected project: openvinotoolkit/openvino β€” Intel OpenVINO Toolkit (~7.8k stars, active development) Class: CWE-190 Integer Overflow β†’ CWE-125 Out-of-bounds Read β€” attacker-controlled offset and size attributes from the .xml model file overflow when added together, bypassing a bounds check. The resulting pointer computation goes out of bounds of the .bin weights buffer. Additionally, shape_size uses unsafe multiplication (safe version exists but is not used in the deserializer). Status: TOOL-VERIFIED under AddressSanitizer (SEGV READ + heap-buffer-overflow READ). 2026-06-12.


Threat model (why this is in MFV scope)

OpenVINO IR (Intermediate Representation) models consist of:

  • .xml file: layer graph topology with tensor shapes, offsets, and sizes
  • .bin file: binary weight data

The standard load path is:

ov::Core core;
auto model = core.read_model("model.xml", "model.bin");
auto compiled = core.compile_model(model, "CPU");
auto infer_request = compiled.create_infer_request();

The .xml file is fully attacker-controlled. It specifies offset and size attributes on <data> elements that tell the deserializer where to read weight data from the .bin buffer. Loading an untrusted .xml + .bin pair causes OOB memory access during model deserialization.


Summary of findings

# Bug Primitive Function / line Trigger Verified
1 offset + size overflow in bounds check OOB READ (SEGV) xml_deserialize_util.cpp:895 offset+size attrs in XML βœ… ASAN
2 Same overflow, string tensor path heap-buffer-overflow READ xml_deserialize_util.cpp:828 offset+size attrs in XML βœ… ASAN
3 shape_size unsafe multiplication validation bypass xml_deserialize_util.cpp:904 shape attr in XML βœ… (logic)
4 Same overflow in preprocessing OOB READ input_model.cpp:136 mean offset+size in XML same pattern

Root cause: arithmetic on attacker-controlled XML attributes without overflow checks, despite OpenVINO having safe arithmetic functions (mul_overflow, shape_size_safe) available but unused in the IR deserializer.


BUG 1 β€” offset+size integer overflow β†’ OOB read (main constant path)

Root Cause

xml_deserialize_util.cpp:893-897 (OpenVINO @ 7cf8606, 2026-06-12):

const auto size = static_cast<size_t>(pugixml::get_uint64_attr(dn, "size"));
const auto offset = static_cast<size_t>(pugixml::get_uint64_attr(dn, "offset"));
OPENVINO_ASSERT(m_weights->size() >= offset + size, "Incorrect weights in bin file!");

char* data = m_weights->get_ptr<char>() + offset;

Both offset and size are attacker-controlled uint64_t values from the XML <data> element. The bounds check at line 895 computes offset + size which can overflow size_t to a small value, causing the assertion to pass.

Example: offset = 0xFFFFFFFFFFFFFF00, size = 0x200

  • offset + size = 0x100 (256) β€” overflows!
  • m_weights->size() = 1024 >= 256 β†’ check passes
  • data = weights_ptr + 0xFFFFFFFFFFFFFF00 β†’ points ~18 EB past the buffer

ASAN Evidence

==ERROR: AddressSanitizer: SEGV on unknown address 0x7cafdb9dff80
  (pc 0x55c807f09669 bp 0x7ffe8a8e34d0 sp 0x7ffe8a8e3420 T0)
The signal is caused by a READ memory access.
    #0 test_offset_size_overflow()  harness_ir.cpp:96

(full log: findings/openvino_evidence/offset_overflow_segv.txt)


BUG 2 β€” Same overflow in string tensor path

xml_deserialize_util.cpp:819-830:

size_t offset = static_cast<size_t>(pugixml::get_uint64_attr(dn, "offset"));
// ...
if (m_weights->size() < offset + size)         // ← SAME overflow bug
    OPENVINO_THROW("Incorrect weights in bin file!");
char* data = m_weights->get_ptr<char>() + offset;
auto buffer = ...unpack_string_tensor(data, size);  // reads 'size' bytes from OOB

This path handles string-type tensor data. With a valid-looking offset (within the buffer) and a size that causes offset + size to wrap, the check passes but unpack_string_tensor reads size bytes from near-end-of-buffer β†’ heap-buffer-overflow READ.

ASAN Evidence

==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7d46d69e0485
READ of size 1 at 0x7d46d69e0485 thread T0
    #0 test_offset_size_overflow_variant2()  harness_ir.cpp:133

0x7d46d69e0485 is located 5 bytes after 1024-byte region [0x7d46d69e0080,0x7d46d69e0480)

(full log: findings/openvino_evidence/offset_overflow_heap_oob.txt)


BUG 3 β€” shape_size unsafe multiplication (safe version exists but unused)

xml_deserialize_util.cpp:904:

if (size < ((ov::shape_size(shape) * el_type.bitwidth() + 7) >> 3)) {
    OPENVINO_THROW("Attribute and shape size are inconsistent...");
}

shape_size (defined at shape.hpp:87-95) uses std::accumulate with std::multiplies β€” no overflow check:

// shape.hpp:92-95 β€” THE UNSAFE VERSION
return std::accumulate(start_dim, end_dim,
                       typename ...:value_type{1},
                       std::multiplies<typename ...:value_type>());

OpenVINO has a safe version at shape_util.cpp:72-80:

// shape_util.cpp:72-80 β€” THE SAFE VERSION (NOT USED IN DESERIALIZER)
std::optional<size_t> shape_size_safe(const Shape& shape) {
    size_t size = 1;
    for (auto first = shape.cbegin(), last = shape.cend(); first != last; ++first) {
        if (mul_overflow(size, *first, size)) {    // uses __builtin_mul_overflow
            return std::nullopt;
        }
    }
    return std::make_optional(size);
}

With crafted shape dims like [2, 2, 2^62]:

  • shape_size() overflows to 0
  • 0 * bitwidth + 7 >> 3 = 0
  • The check size < 0 fails (any size β‰₯ 0) β†’ validation passes
  • A Constant node is created with a shape implying 2^65 elements but only size bytes of data
  • Any operation iterating shape dimensions reads past the buffer

Evidence

shape_size() = 0 (overflowed from true value > 2^64)
Validation PASSED: xml_size (1024) >= overflowed check (0)
Shape implies 2^65 elements but buffer is only 1024 bytes.

(full log: findings/openvino_evidence/shape_size_overflow.txt)


BUG 4 β€” Same offset+size overflow in preprocessing

input_model.cpp:136:

if (const_offset + const_size > weights->size()) {
    OPENVINO_THROW("mean value offset and size are out of weights size range");
}

Same pattern: const_offset and const_size from XML preprocessing <mean> element, addition can overflow.


Inconsistent hardening assessment

OpenVINO demonstrates inconsistent use of safe arithmetic:

Component Function Safe? Notes
shape_util.cpp shape_size_safe βœ… Uses mul_overflow
memory_util.cpp get_memory_size_safe βœ… Uses mul_overflow
common_util.hpp mul_overflow βœ… Uses __builtin_mul_overflow
runtime/tensor.cpp allocation βœ… Uses get_memory_size_safe
xml_deserialize_util.cpp:895 bounds check ❌ Plain addition, no overflow check
xml_deserialize_util.cpp:828 bounds check ❌ Plain addition, no overflow check
xml_deserialize_util.cpp:904 shape validation ❌ Uses shape_size not shape_size_safe
input_model.cpp:136 bounds check ❌ Plain addition, no overflow check

The safe functions exist and are used in other code paths β€” the IR deserializer simply doesn't use them.


Affected versions

  • openvinotoolkit/openvino @ HEAD (7cf8606, 2026-06-12): AFFECTED.
  • All versions using the current XmlDeserializer::on_adapter implementation.
  • The Python bindings (openvino pip package) that call read_model are also affected.

Reproduction

# Build harness with ASAN:
g++ -std=c++17 -fsanitize=address -g -O0 -o harness_ir harness_ir.cpp

# Test 1: offset+size overflow β†’ SEGV (huge offset)
./harness_ir

# Test 2: offset+size overflow β†’ heap-buffer-overflow (valid offset, huge size)
./harness_ir variant2

# Test 3: shape_size overflow (validation bypass)
./harness_ir shape

Build: poc/mfv_openvino_ir.cpp Build flags: g++ -std=c++17 -fsanitize=address -g -O0


Remediation

  1. BUG 1+2+4: Replace plain offset + size with overflow-checked addition:

    size_t sum;
    OPENVINO_ASSERT(!__builtin_add_overflow(offset, size, &sum) && m_weights->size() >= sum,
                    "Incorrect weights in bin file!");
    
  2. BUG 3: Use shape_size_safe instead of shape_size:

    auto maybe_shape_size = ov::util::shape_size_safe(ov::Shape(shape.begin(), shape.end()));
    OPENVINO_ASSERT(maybe_shape_size.has_value(), "Shape dimensions overflow");
    if (size < ((maybe_shape_size.value() * el_type.bitwidth() + 7) >> 3)) {
        OPENVINO_THROW("...");
    }
    
  3. General: Audit all XML-derived arithmetic in the IR frontend for consistent use of safe functions.


Novelty / dedup

  • No CVE found for integer overflow in OpenVINO's IR XML deserializer offset+size handling.
  • No prior huntr disclosure for OpenVINO IR format found.
  • OpenVINO has received prior security fixes (CVE-2024-* series) but none for this specific pattern.
  • Dup risk: low-medium. The inconsistent safe/unsafe pattern suggests this was overlooked.

Suggested CVSS

AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H β†’ 7.1 (local file, user loads model; OOB read β†’ information disclosure + crash). Note: this is OOB READ not WRITE β€” exploitability for code execution is lower than the WRITE bugs in other targets, but information disclosure (memory contents leaked through model outputs) is possible.

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