--- 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.