# PoC — NeMo `setup_model()` target-allowlist bypass `malicious_target_bypass.nemo` is an uncompressed tar archive (`tarfile.open(mode="w:")`, the same mode NeMo itself uses to write `.nemo` files) containing: - `model_config.yaml` — a minimal config whose top-level `target` field is `subprocess.Popen`, a class completely unrelated to any NeMo model and **not** present in `nemo.core.classes.common.ALLOWED_TARGET_PREFIXES`. - `model_weights.ckpt` — a placeholder (not a real PyTorch checkpoint); it is never reached in this specific demonstration because `subprocess.Popen` has no `.restore_from()`. ## How this is meant to be used ``` python examples/asr/transcribe_speech.py model_path=malicious_target_bypass.nemo audio_dir= ``` Expected effect, per the vulnerability described in the accompanying `SUBMISSION.md`: 1. `setup_model()` (`nemo/collections/asr/parts/utils/transcribe_utils.py:315-344`) calls `ASRModel.restore_from(restore_path=model_path, return_config=True)`, which extracts and parses `model_config.yaml` **without** ever calling `_is_target_allowed()` / `safe_instantiate()`. 2. `classpath = model_cfg.target` → `"subprocess.Popen"`. 3. `model_utils.import_class_by_path("subprocess.Popen")` succeeds — this is the raw, unprotected function; it imports `subprocess` and resolves `Popen` with no allowlist check at all. 4. `subprocess.Popen.restore_from(restore_path=..., map_location=...)` is then attempted and raises `AttributeError` (`Popen` has no `restore_from`), so the process crashes at this point. The crash on step 4 is the **intentionally non-destructive, minimum-guaranteed** demonstration: it proves the import of an arbitrary, attacker-chosen, out-of-allowlist class already happened (step 3) before anything fails. Swapping `subprocess.Popen` for a module with dangerous import-time side effects, or for a compatible internal NeMo model class, changes the outcome without changing the underlying bug. ## Caveat — not run end-to-end against a live NeMo installation The exact bypass mechanism (`import_class_by_path()` accepting `"subprocess.Popen"` while `_is_target_allowed()` rejects it) **was** empirically verified by importing and calling the real, unmodified functions from this commit's `nemo/utils/model_utils.py` and `nemo/core/classes/common.py` directly (see `attachments/poc_allowlist_bypass.py` + `attachments/run_output.txt` in the main submission folder). What was **not** independently re-verified is running `transcribe_speech.py` end-to-end against this exact `.nemo` file inside a full NeMo installation (setting up the complete pinned dependency stack — `lightning`, `nv_one_logger*`, `megatron-core`, etc. — proved impractical in the available verification environment due to unrelated third-party version incompatibilities in the `nv_one_logger` / `lightning` integration layer, not to anything about this bug). The static trace of `setup_model()` → `restore_from(..., return_config=True)` → `load_config_and_state_dict()` (`nemo/core/connectors/save_restore_connector.py:98-202`) was read in full and is quoted in the submission; only the live end-to-end run was not reproduced. If it does not reproduce cleanly against a specific NeMo install, the standalone function-level PoC remains valid, verified, independent evidence of the root-cause bug regardless.