CVE-2026-55093
tract-nnef: integer overflow in NNEF `.dat` tensor parser yields an out-of-bounds read on model load
Tract is a tiny, no-nonsense, self-contained TensorFlow and ONNX inference toolkit. Prior to 0.21.16, 0.22.2, and 0.23.1, tract-nnef uses unchecked usize multiplication in nnef/src/tensors.rs read_tensor for attacker-controlled tensor dimensions, the allocation size, and the reported tensor length. Loading a crafted NNEF archive through model_for_path or model_for_read reaches the default DatLoader and can make the wrapped size check accept a small allocation while data/src/tensor.rs as_slice_unchecked creates a much larger logical slice. Model construction through as_uniform can then read beyond the heap allocation and disclose adjacent data, and later access can terminate the process with a segmentation fault. The affected dense numeric tensor path does not include the independently guarded bool, String, or block-quant paths, and no out-of-bounds write or code execution was demonstrated. This issue is fixed in versions 0.21.16, 0.22.2, and 0.23.1.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Required
What an attacker can affect
- Confidentiality
- Low
- Integrity
- None
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
6.1
tract-nnef: integer overflow in NNEF `.dat` tensor parser yields an out-of-bounds read on model load
Tract is a tiny, no-nonsense, self-contained TensorFlow and ONNX inference toolkit. Prior to 0.21.16, 0.22.2, and 0.23.1, tract-nnef uses unchecked usize multiplication in nnef/src/tensors.rs read_tensor for attacker-controlled tensor dimensions, the allocation size, and the reported tensor length. Loading a crafted NNEF archive through model_for_path or model_for_read reaches the default DatLoader and can make the wrapped size check accept a small allocation while data/src/tensor.rs as_slice_unchecked creates a much larger logical slice. Model construction through as_uniform can then read beyond the heap allocation and disclose adjacent data, and later access can terminate the process with a segmentation fault. The affected dense numeric tensor path does not include the independently guarded bool, String, or block-quant paths, and no out-of-bounds write or code execution was demonstrated. This issue is fixed in versions 0.21.16, 0.22.2, and 0.23.1.
- Record
CVE-2026-55093- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H - Weakness
- CWE-125, CWE-190
- Updated
- 2026-11-02
EUVDEUVD-2026-77792
6.1
Tract is a tiny, no-nonsense, self-contained TensorFlow and ONNX inference toolkit. Prior to 0.21.16, 0.22.2, and 0.23.1, tract-nnef uses unchecked usize multiplication in nnef/src/tensors.rs read_tensor for attacker-controlled tensor dimensions, the allocation size, and the reported tensor length. Loading a crafted NNEF archive through model_for_path or model_for_read reaches the default DatLoader and can make the wrapped size check accept a small allocation while data/src/tensor.rs as_slice_unchecked creates a much larger logical slice. Model construction through as_uniform can then read beyond the heap allocation and disclose adjacent data, and later access can terminate the process with a segmentation fault. The affected dense numeric tensor path does not include the independently guarded bool, String, or block-quant paths, and no out-of-bounds write or code execution was demonstrated. This issue is fixed in versions 0.21.16, 0.22.2, and 0.23.1.
- Record
EUVD-2026-77792- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-x5mv-8wgw-29hg
6.1
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…
- Record
GHSA-x5mv-8wgw-29hg- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H - Weakness
- CWE-125, CWE-190
- Updated
- 2026-11-02
NVD
6.1
Tract is a tiny, no-nonsense, self-contained TensorFlow and ONNX inference toolkit. Prior to 0.21.16, 0.22.2, and 0.23.1, tract-nnef uses unchecked usize multiplication in nnef/src/tensors.rs read_tensor for attacker-controlled tensor dimensions, the allocation size, and the reported tensor length. Loading a crafted NNEF archive through model_for_path or model_for_read reaches the default DatLoader and can make the wrapped size check accept a small allocation while data/src/tensor.rs as_slice_unchecked creates a much larger logical slice. Model construction through as_uniform can then read beyond the heap allocation and disclose adjacent data, and later access can terminate the process with a segmentation fault. The affected dense numeric tensor path does not include the independently guarded bool, String, or block-quant paths, and no out-of-bounds write or code execution was demonstrated. This issue is fixed in versions 0.21.16, 0.22.2, and 0.23.1.
- Record
CVE-2026-55093- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H - Weakness
- CWE-125, CWE-190
- Updated
- 2026-11-02
OSV
6.1
tract-nnef: integer overflow in NNEF `.dat` tensor parser yields an out-of-bounds read on model load
Tract is a tiny, no-nonsense, self-contained TensorFlow and ONNX inference toolkit. Prior to 0.21.16, 0.22.2, and 0.23.1, tract-nnef uses unchecked usize multiplication in nnef/src/tensors.rs read_tensor for attacker-controlled tensor dimensions, the allocation size, and the reported tensor length. Loading a crafted NNEF archive through model_for_path or model_for_read reaches the default DatLoader and can make the wrapped size check accept a small allocation while data/src/tensor.rs as_slice_unchecked creates a much larger logical slice. Model construction through as_uniform can then read beyond the heap allocation and disclose adjacent data, and later access can terminate the process with a segmentation fault. The affected dense numeric tensor path does not include the independently guarded bool, String, or block-quant paths, and no out-of-bounds write or code execution was demonstrated. This issue is fixed in versions 0.21.16, 0.22.2, and 0.23.1.
- Record
CVE-2026-55093- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H - Weakness
- CWE-125, CWE-190
- Updated
- 2026-11-02
OSVGHSA-x5mv-8wgw-29hg
6.1
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…
- Record
GHSA-x5mv-8wgw-29hg- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H - Weakness
- CWE-125, CWE-190
- Updated
- 2026-11-02
OSVRUSTSEC-2026-0217
6.1
Integer overflow in tract-nnef NNEF tensor parser leads to out-of-bounds read on model load
tract_nnef::tensors::read_tensor builds a tensor shape from attacker-controlled
32-bit dimensions and computes both the element count product(shape) and the
byte allocation product(shape) * size_of(dt) with unchecked usize
arithmetic. In release builds (no overflow-checks) both products wrap modulo
2^64.
A crafted NNEF .dat tensor can choose dimensions whose wrapped products
collapse to a small value that satisfies the header size-consistency check, while
the true element count stays astronomically large. read_tensor then returns a
Tensor whose reported len (e.g. 2^61 + 7) far exceeds its backing heap
allocation (e.g. 56 bytes). The unchecked accessor as_slice_unchecked
(slice::from_raw_parts(ptr, self.len())) subsequently yields a slice spanning
~18 EiB over the small buffer.
The out-of-bounds read fires automatically during model build (no inference
required), reachable through the default DatLoader resource loader via the
public tract_nnef::nnef().model_for_path / model_for_read API when the
const-folding as_uniform fast-path materializes the over-long constant. The
always-on primitive is a bounded adjacent-heap over-read (information
disclosure); access further past the mapped region SIGSEGVs (denial of service).
No out-of-bounds write or code execution was demonstrated.
Affected: every release line prior to the backported fixes — < 0.21.16,
0.22.0–0.22.1, and 0.23.0. The block-quant path had already received an
analogous blob-size guard; the dense DatLoader path was missed.
Mitigation
Upgrade to 0.21.16, 0.22.2, or 0.23.1. The fix computes the shape product
and byte size with checked_mul and rejects the tensor on overflow
(commit 34c7df2).
- Record
RUSTSEC-2026-0217- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
tract-nnefcrates.io |
0.23.0 up to 0.23.1 | 0.23.1 |
tract-nnefcrates.io |
0.22.0 up to 0.22.2 | 0.22.2 |
tract-nnefcrates.io |
before 0.21.16 | 0.21.16 |
tract-nnefcrates.io |
0.0.0-0 up to 0.21.16, 0.22.0 up to 0.22.2, 0.23.0 up to 0.23.1 | 0.21.160.22.20.23.1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-55093 is a medium vulnerability (CVSS 6.1) in Sonos Tract, published June 18, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.18%, the 7th percentile. Fixed in tract-nnef 0.23.1.
References
github.com/sonos/tract/security/advisories/GHSA-x5mv-8wgw-29hg
github.com/sonos/tract/commit/34c7df2c9bd2a36583e09b52f3e6319bf23102e8
github.com/sonos/tract/commit/828ec20f74c14c0923c7d7f0cff062ed222d630a
github.com/sonos/tract/commit/ee5f019d39fb97f7d4431b883c3ce97b74c28a64
github.com/sonos/tract/releases/tag/0.21.16
github.com/sonos/tract/releases/tag/0.22.2
Show 7 moreShow less
Left out here: the rest of 2 source descriptions. The JSON record has them.