🛡️ CVE-2026-55093 — tract-nnef
Description
tract-nnef: integer overflow in NNEF .dat tensor parser yields an out-of-bounds read on model load
- Component:
tract-nnef(nnef/src/tensors.rs::read_tensor) +tract-data(data/src/tensor.rs) - Affected versions:
< 0.21.16,0.22.0–0.22.2,0.23.0–0.23.1— the denseDatLoaderpath was unguarded across all three release lines; patched in 0.21.16 / 0.22.2 / 0.23.1 - Class: CWE-190 (integer overflow) → CWE-125 (out-of-bounds read)
- Trigger: loading a crafted NNEF model archive (
*.nnef.tgz/*.nnef.tar/ dir) via the publictract_nnef::nnef().model_for_path/model_for_read - Impact:
read_tensorreturns a memory-unsafe tensor (reportedlen2^61 over a 56-byte heap allocation). Always-on primitive: a bounded heap out-of-bounds read during model build (as_uniform), an adjacent-heap information-disclosure reachable via the public load API. The resulting slice is an unsoundfrom_raw_parts(ptr, 2^61)that SIGSEGVs (DoS) on any access past the mapped region (demonstrated by direct access). No out-of-bounds write and no RCE were achieved — tract's const-folding/as_uniformfast-paths fold simple consuming graphs without the full read. - Severity: Medium
Summary
read_tensor builds a tensor shape from attacker-controlled 32-bit dimensions and computes the element count len = product(shape) and the byte allocation product(shape) * size_of(dt) with unchecked usize arithmetic. In --release (no overflow-checks), both products wrap modulo 2^64. An attacker chooses dimensions so that the wrapped products collapse to a small value that satisfies the header consistency check, while the *true* element count remains astronomically large. read_tensor returns Ok with a Tensor whose reported len (e.g. 2^61+7) is far larger than its backing heap allocation (e.g. 56 bytes). The unchecked slice accessor as_slice_unchecked (from_raw_parts(ptr, self.len)) then produces a slice spanning ~18 exabytes over a 56-byte buffer. The out-of-bounds read fires automatically during model build (no inference required), reachable through the default DatLoader resource loader.
Root cause
nnef/src/tensors.rs, read_tensor:
```
let shape: TVec<usize> = header.dims[0..header.rank as usize].iter().map(|d| *d as _).collect();
let len = shape.iter().product::<usize>(); // (1) unchecked, wraps
...
} else if header.bits_per_item != u32::MAX
&& len * (header.bits_per_item as usize / 8) != header.data_size_bytes as usize // (2) wrapped == u32
{
bail!(...);
}
...
let mut tensor = unsafe { Tensor::uninitialized_dt(dt, &shape)? }; // (3) alloc off the same wrapped product
...
reader.read_exact(plain.as_bytes_mut())?; // storage-bounded read, no overflow here
Ok(tensor)
```
data/src/tensor.rs, uninitialized_aligned_dt:
```
let bytes = shape.iter().cloned().product::<usize>() * dt.size_of(); // (3) wraps to the same small value
let storage = ... Blob::new_for_size_and_align(bytes, alignment) ...;
...
tensor.update_strides_and_len(); // len = product(shape), wraps, no clamp
```
The three quantities — the consistency-check LHS (2), the allocation (3), and the reported len — are all the same wrapped product(shape)*size_of, so they stay mutually consistent and the consistency check at (2) cannot catch the overflow. data_size_bytes is a u32, so the attacker simply sets it to the wrapped value.
Corruption sink — data/src/tensor.rs::as_slice_unchecked (and data/src/tensor/plain_view.rs::as_slice_unchecked):
```
if self.storage.byte_len() == 0 { &[] }
else { std::slice::from_raw_parts(self.as_ptr_unchecked(), self.len()) } // len = 2^61 over a 56-byte alloc
```
The only guard is byte_len() == 0. A small non-zero allocation defeats it and yields an unsound oversized slice.
Witness (F64)
```
dims = [33955849, 7005787, 359, 3, 3, 3] (rank 6, each <= u32::MAX)
product(shape)= 2_305_843_009_213_693_959 = 2^61 + 7
bits_per_item = 64 (F64), item_type = 0, item_type_vendor = 0
data_size_bytes = 56 # == (2^61+7)*8 mod 2^64
```
len * (bits/8) mod 2^64 = (2^61+7)*8 mod 2^64 = 56 == data_size_bytes→ consistency check passes.- allocation =
(2^61+7)*8 mod 2^64 = 56bytes (7 × F64). - reported
len=2^61+7elements.
Only the is_copy() numeric arms (F16/F32/F64/int, and likely the complex arms) are exploitable. F64 is the cleanest (bits/8 divides evenly). The bool, String, and block-quant paths are each guarded by an independent mechanism (size_of==1 prevents byte/element divergence; String bails on a missing num_traits::Zero impl; block-quant has its own ensure!(expected_len == data_size_bytes) and uses non-plain Exotic storage).
Reachability (load-time, public API)
```
nnef().model_for_read(tar)
-> proto_model_for_read nnef/src/framework.rs:303
-> Da
How this vulnerability can be exploited
This issue can be reached with local access to the system, attack complexity is low, an attacker needs no privileges on the target. A user must be tricked into taking some action. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality low, integrity none, availability high.
Weakness class
CVE-2026-55093 is classified as CWE-125: Out-of-bounds Read. The code reads past the limits of a buffer, exposing adjacent memory contents or crashing the process.
Affected software
CVE-2026-55093 is recorded against 1 package.
- tract-nnef
Timeline and source
Published on 18 June 2026 and last revised on 28 July 2026. No public exploit is currently recorded for this entry. Record sourced from OSV.
References
Details
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| tract-nnef | — | — |
References
More CVE 2026 advisories
Browse all of CVE 2026 in the advisory index.
Free Vulnerability Check
Is your site affected by CVE-2026-55093?
BotEraser helps you identify potentially vulnerable plugins and themes by checking your installation against CVE-2026-55093 and other known CVE records.
Scan My Site Free →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.