You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

NeMo .nemo restore RCE via legacy top-level target key β€” incomplete fix of CVE-2025-23304

Severity: High (config-injection RCE on model load; bypasses the post-CVE allow-list) Affected tool: nemo_toolkit 2.7.3 (latest; branch also present on NVIDIA-NeMo/NeMo main). Entry point: nemo.core.ModelPT.restore_from / from_pretrained β†’ SaveRestoreConnector.load_config_and_state_dict β†’ Serialization.from_config_dict. Category: Model-loader RCE on .nemo (NVIDIA's primary distribution format).

Summary

CVE-2025-23304 was fixed by adding safe_instantiate() / _is_target_allowed() / _validate_config_targets_recursive() that validate the Hydra _target_ key against an allow-list before instantiation. The fix never inspects the legacy target key. from_config_dict has a third, unguarded branch: when a config has top-level target (but no params/_target_) it calls import_class_by_path(config['target']) β€” an unrestricted __import__ + getattr β€” then imported_cls(cfg=config), with no allow-list check. Because ModelPT.__init__ injects cfg.target = "<module>.<Class>" into every saved config (and _convert_config deliberately leaves target unconverted), every real .nemo lands in exactly this branch on restore. An attacker swaps the target: line in any .nemo's model_config.yaml to an arbitrary importable path β†’ import-time code exec + arbitrary class construction.

Root cause

  • Vulnerable branch: nemo/core/classes/common.py:605-623 β€” else path: imported_cls = import_class_by_path(config['target']) (:614, unrestricted, confirmed at model_utils.py:564), then imported_cls(cfg=config[, trainer]) (:621/:623). No allow-list.
  • Allow-list that misses it: common.py:90-170 (_is_target_allowed / _validate_config_targets_recursive / safe_instantiate) only ever inspects _target_.
  • Universal reachability: modelPT.py:128-131 injects top-level target into every saved config; model_utils._convert_config converts only clsβ†’_target_ and explicitly leaves target unchanged.
  • Entry point: save_restore_connector.py:168 (OmegaConf.load of the attacker-controlled model_config.yaml) and :191 (calling_cls.from_config_dict(config=conf)) β€” the exact call restore_from makes. The hardened tar guard (CVE-2024-0129/CVE-2025-23360) and weights_only=True (CVE-2025-23303) do not touch this config path.

Reproduce

python poc/poc_real.py β€” uses 100% real NeMo code (only unrelated nv_one_logger telemetry stubbed). Builds a malicious .nemo whose model_config.yaml has target: evil_gadget.Pwn, runs the real guarded extract + OmegaConf.load + from_config_dict. Output (exit 0): arbitrary module imported = True, attacker class constructed = True, attacker file written = True, safe_instantiate() consulted = False, _is_target_allowed('evil_gadget.Pwn') = False (proving it would be blocked as _target_). poc/poc_realworld_gadget.py proves it with real stdlib: target='this.X' fires import-time exec (prints the Zen of Python); target='argparse.Namespace' constructs a real arbitrary class. WebFetch of NVIDIA-NeMo/NeMo main confirms the unguarded if 'target' in config: import_class_by_path(...) branch is still present.

Impact

Loading an untrusted .nemo via the documented restore_from() / from_pretrained() API executes attacker-chosen code with the victim's privileges β€” no pickle, bypassing the allow-list users now rely on post-CVE-2025-23304. The malicious file is a normal-looking tar whose model_config.yaml simply swaps its target: line (survives casual inspection + current pickle/tar scanners). High blast radius: .nemo is NVIDIA's primary format with 700+ HF models (parakeet/Canary ASR), and restore_from auto-downloads remote checkpoints.

Dup-check

Novel incomplete-fix sibling of CVE-2025-23304 (the _target_ Hydra bug, Unit42; the Unit42 writeup uses _target_ and does not mention the legacy target key, ModelPT injection, import_class_by_path, or the from_config_dict fallback). Other NeMo CVEs are unrelated vectors: CVE-2024-0129/CVE-2025-23360 (tar traversal), CVE-2025-23303 + CVE-2026-24157/24159 (pickle/torch.load deserialization, already patched in 2.7.3). PR #13607 (CVE fix) and PR #14540 (_is_target_allowed hardening) both touched only _target_. gh search for import_class_by_path target / from_config_dict safe_instantiate returned no matching reports. No CVE/GHSA covers the legacy target path.

Scope caveat (honest): shares the instantiation-RCE surface with CVE-2025-23304 and could be triaged as an extension of that fix β€” but it is a genuinely separate, unguarded branch present on latest + main.

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support