Instructions to use testamentaria/nemo with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- NeMo
How to use testamentaria/nemo with NeMo:
# tag did not correspond to a valid NeMo domain.
- Notebooks
- Google Colab
- Kaggle
File size: 3,383 Bytes
d8c40b3 | 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 | # 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=<any>
```
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.
|