GitHub Advisoryadvisory · crates.io

GHSA-x5mv-8wgw-29hg tract-nnef: integer overflow in NNEF `.dat` tensor parser yields an out-of-bounds read on model load

MEDIUM Published 2026-06-18 Modified 2026-06-18 Official page ↗
Aliases
CVE-2026-55093
Ecosystem
crates.io
Packages
tract-nnef
Severity
Moderate
CVSS vector
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H
Weaknesses
CWE-125, CWE-190
Reviewed
2026-06-18

Affected packages

• crates.io tract-nnef: >= 0.23.0, < 0.23.1 → fixed in 0.23.1 • crates.io tract-nnef: >= 0.22.0, < 0.22.2 → fixed in 0.22.2 • crates.io tract-nnef: < 0.21.16 → fixed in 0.21.16

Description

• Component: `tract-nnef` (`nnef/src/tensors.rs::read_tensor`) + `tract-data` (`data/src/tensor.rs`) • Affected versions: ` = header.dims[0..header.rank as usize].iter().map(|d| *d as _).collect(); let len = shape.iter().product::(); // (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::() * 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 proto_model_for_read nnef/src/framework.rs:303 -> DatLoader.try_load (any *.dat) nnef/src/resource.rs:97 (default loader, framework.rs:33) -> read_tensor -> Ok(Tensor{len=2^61+7, storage=56B}) nnef/src/tensors.rs:61 -> into_typed_model -> variable() fragment nnef/src/ops/nnef/deser.rs:74 ensure!(tensor.shape() == &*shape) deser.rs:122 (attacker matches shape in graph.nnef -> passes) -> Const::new -> wire_node core/src/model/typed.rs:67 -> Const::output_facts core/src/ops/konst.rs:54 -> TypedFact::try_from core/src/model/fact.rs:459 -> Tensor::as_uniform -> is_uniform_t:: data/src/tensor.rs:1099 -> as_slice_unchecked:: data/src/tensor.rs:1044 -> from_raw_parts(ptr, 2^61+7) over 56-byte buffer -> OOB READ ``` No shape-vs-storage re-validation exists anywhere on this path (`proto.validate()` checks only the AST; `Const::new` checks only `is_plain`; `check_for_access` checks only the datum type; even the *safe* `PlainView::as_slice` does `from_raw_parts(ptr, self.len)` with no length guard). Execution (proof of concept) Reproduced against the crate at the affected revision, `--release`, x86_64-linux. Three scenarios: 1. Direct `read_tensor` — feed the crafted 128-byte header + 56-byte payload: • `read_tensor -> Ok`, `shape=[33955849,7005787,359,3,3,3]`, `len()=2305843009213693959`, `as_bytes().len()=56`, `as_slice::().len()=2305843009213693959`. • `s[7]` (first element past the 56-byte allocation) returns `0x0000000000000041` → heap OOB read (adjacent-heap disclosure). • `s[1()` over externally-controlled dimensions without `checked_*`/`try_into`, especially when the result feeds an allocation and a separately-tracked `len`. • Runtime / fleet: crash telemetry showing SIGSE…

Related

Alias · CVE (1)

  • CVE-2026-55093 Tract is a tiny, no-nonsense, self-contained TensorFlow and ONNX inference toolkit. vulnerability

Weakness · CWE (2)

  • CWE-125 Out-of-bounds Read weakness
  • CWE-190 Integer Overflow or Wraparound weakness

References