Skip to content

[Security] Model load-time code execution: ONNX metadata strings evaluated with eval() in the vendored ultralytics copy (models/MobileSAMv2) #145

Open

Description

@3em0

Summary

This repository vendors ultralytics 8.0.120 inside a vendored sub-project (models/MobileSAMv2/ultralytics/…), and that copy still parses ONNX model metadata with eval(). Loading an untrusted .onnx therefore executes attacker-chosen Python in the loading process — before any inference, and before the pickle-free path that leads people to choose ONNX in the first place.

Affected version

  • bundled ultralytics 8.0.120 (inside models/MobileSAMv2/)
  • repository HEAD 5888e722876511f177e8e762498a7986753b7f7d (last push 2025-09-08; re-checked 2026-09-29, unchanged)
  • models/MobileSAMv2/ultralytics/nn/autobackend.py:132 — metadata = session.get_modelmeta().custom_metadata_map
  • models/MobileSAMv2/ultralytics/nn/autobackend.py:278 — metadata[k] = eval(v)
  • file sha256 55a5363f49f0a63d18d07c01264fabd7e3fab7cfbdeb9125e5bf43d642b0c4d8 (byte-identical to the file CASIA-LMC-Lab/FastSAM ships at the same 8.0.120 vintage)

Steps to reproduce

  1. Build a minimal .onnx whose metadata_props carries a constant-only expression for imgsz (for example "7*191").
  2. Load it through the vendored code: AutoBackend(weights="model.onnx") — the library's ordinary action for a model file.
  3. The expression runs while the constructor executes, and imgsz comes back as [640, 640] — a value only eval() of the stored string could produce.

Expected behavior

metadata_props is inert data; imgsz / names / kpt_shape are parsed, not executed.

Actual behavior

The stored strings are evaluated with the module globals and unrestricted builtins. Between the read at :132 and the eval at :278 there is no allow-list, no regex, no restricted builtins and no try/except. imgsz, names and kpt_shape are exactly the keys a genuine exported model carries, and ONNX Runtime places no constraint on metadata_props.

How it was verified

Code-level: the source and sink lines above were re-read live at this repository's default branch HEAD on 2026-09-29. Execution-level: the exact bytes this repository vendors were executed on 2026-09-29 — the official PyPI ultralytics 8.0.120 wheel, whose nn/autobackend.py is byte-identical to the vendored file (same sha256 55a5363f…), was driven through AutoBackend(weights=…) with a harmless sentinel expression in the metadata (it only writes one inert marker file) and a benign-metadata negative control: the marker appears for the attack artifact, never for the control. A sibling vendored copy of the same lineage (iMoonLab/yolov13, 8.3.63) was additionally driven end-to-end through the product's own yolo predict CLI. No exploit string is included here; the sentinels are harmless integer constants — no network, no shell, no persistence.

Impact

Arbitrary code execution with the privileges of the loading process — anything that process can read or write (datasets, checkpoints, hub tokens, cloud credentials), plus the ability to terminate or hang it.

Suggested fix

Apply the one-line upstream fix at models/MobileSAMv2/ultralytics/nn/autobackend.py:278, or resync the vendored sub-project to >= 8.3.234. ast is already imported in the file:

-                    metadata[k] = eval(v)
+                    metadata[k] = ast.literal_eval(v)

Longer term it may be worth depending on the PyPI ultralytics package (>= 8.3.234) instead of vendoring a copy of a copy.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions