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
- Threat model (why this is in MFV scope)
- Summary of findings
- BUG 1 β offset+size integer overflow β OOB read (main constant path)
- BUG 2 β Same overflow in string tensor path
- BUG 3 β shape_size unsafe multiplication (safe version exists but unused)
- BUG 4 β Same offset+size overflow in preprocessing
- Inconsistent hardening assessment
- Affected versions
- Reproduction
- Remediation
- Novelty / dedup
- Suggested CVSS
- Threat model (why this is in MFV scope)
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:
.xmlfile: layer graph topology with tensor shapes, offsets, and sizes.binfile: 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 passesdata = 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 00 * bitwidth + 7 >> 3 = 0- The check
size < 0fails (any size β₯ 0) β validation passes - A Constant node is created with a shape implying 2^65 elements but only
sizebytes 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_adapterimplementation. - The Python bindings (
openvinopip package) that callread_modelare 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
BUG 1+2+4: Replace plain
offset + sizewith overflow-checked addition:size_t sum; OPENVINO_ASSERT(!__builtin_add_overflow(offset, size, &sum) && m_weights->size() >= sum, "Incorrect weights in bin file!");BUG 3: Use
shape_size_safeinstead ofshape_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("..."); }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.