Skip to content

Deterministic panic (DoS) via .unwrap() on absent TensorProto field in tract ONNX loader #2372

Description

@professor-moody

Summary

get_attr_opt_scalar<&TensorProto> in tract's ONNX helper code retrieves a TensorProto from a node
attribute with .map(|attr| attr.t.as_ref().unwrap()). The ONNX AttributeProto.t field is optional
— it can be absent in a well-formed protobuf even when attribute_type == TENSOR. When t is unset,
attr.t.as_ref() is None and .unwrap() panics. Because Rust panics propagate regardless of
optimization level, this is a deterministic release panic (DoS) on loading any ONNX model with a
Tensor-type attribute whose t field is missing.

Affected component

  • Repository: sonos/tract (tract-onnx crate)
  • File: tract-onnx/src/pb_helpers.rs:114.map(|attr| attr.t.as_ref().unwrap()) in
    get_attr_opt_scalar<&TensorProto>.
  • Reachable from onnx().model_for_read() / model_for_path() — any ONNX model load. The confirmed
    path goes through the Constant op handler (ops/mod.rs:53) during graph construction; any op that
    reads a Tensor-type attribute via get_attr_opt::<&TensorProto>() is similarly affected.

Reproduction

PoC: findings/tract/poc-085-tract-onnx-attr-tensor-unwrap.onnx (86 bytes) — an ONNX model with a
Tensor-type attribute whose t field is unset.

Load via tract_onnx::onnx().model_for_read() (or model_for_path()). Observed:

thread panicked at tract-onnx/src/pb_helpers.rs:114:41:
called `Option::unwrap()` on a `None` value

(via get_attr_opt_scalar.unwrap() on the absent t field).

Root cause

// tract-onnx/src/pb_helpers.rs:111-116
impl<'a> AttrScalarType<'a> for &'a TensorProto {
    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {
        Ok(node
            .get_attr_opt_with_type(name, AttributeType::Tensor)?
            .map(|attr| attr.t.as_ref().unwrap()))  // panics when t is None
    }
}

get_attr_opt_with_type returns Some(attr) when it finds a matching-type attribute, but
attr.t (Option<Box<TensorProto>>) can still be None. The .unwrap() turns that absent field
into a process panic.

Suggested fix

Return an error instead of unwrapping:

impl<'a> AttrScalarType<'a> for &'a TensorProto {
    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {
        Ok(node
            .get_attr_opt_with_type(name, AttributeType::Tensor)?
            .map(|attr| attr.t.as_ref()
                .ok_or_else(|| anyhow::anyhow!(
                    "attribute '{}' has type TENSOR but t field is absent", name)))
            .transpose()?)
    }
}

Revalidation

Source-level revalidation on 2026-06-14 against tract HEAD 5ac3908: onnx/src/pb_helpers.rs:114
still maps with attr.t.as_ref().unwrap() and is unguarded — the release panic is intact. The tract
Sonos email window (opened 2026-05-28) has elapsed, so this is re-routing to a public issue.

Status: advisory drafted; re-routing to public issue + VulDB; revalidated 2026-06-14 source-level vs
tract HEAD 5ac3908.

Proof-of-concept files (base64)

Decode with base64 -d > file.

poc-085-tract-onnx-attr-tensor-unwrap.onnx (86 bytes):

CA06TAovEgFZIghDb25zdGFudCogCgV2YWx1ZSIUCAMQASIMAACAPwAAAEAAAEBAQgCgAQQSCGNvbnN0YW50Yg8KAVkSCgoICAESBAoCCANCBAoAEBE=

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions