Summary
## TL;DR Loading an attacker-supplied lemma `.pt` runs arbitrary code as the loading user. Since 1.14.0 the lemmatizer stores `pos_dict` as a gzip-compressed pickle in the checkpoint's `dicts` field (a `bytes` value). `torch.load(weights_only=True)` treats `bytes` as a primitive and passes it through unexamined, so its allowlist never sees the inner stream. `Trainer.load` then feeds those bytes to an **unrestricted `pickle.loads`**: the only bare `pickle.loads` in the tree. Result: `weights_only=True` gives no protection here, and model loading executes code. Confirmed end-to-end against stanza 1.14.0 / PyTorch 2.13.0 / CPython 3.13.7: a crafted checkpoint whose payload `__reduce__` returns `(os.system, (cmd,))` ran the command at load time (marker file showed `uid=501(...)`, `executed_by=...`). A downstream user is vulnerable whenever they build a pipeline that loads a caller-supplied lemmatizer checkpoint. i.e. `stanza.Pipeline(lang=..., processors="tokenize,pos,lemma", lemma_model_path="<attacker-influenced .pt>")` (or any equivalent load of an untrusted/shared lemma model through a custom registry) - since that call routes straight into `Trainer.load` -> `_unpack_pos_dict` and executes the embedded pickle at construction time. ## The vulnerable path `stanza/models/lemma/trainer.py`: ```python # _pack_pos_dict (write side): pos_dict -> pickle -> gzip -> bytes, stored under checkpoint['dicts'] def _unpack_pos_dict(data): # line 54 buf = io.BytesIO(data) with gzip.GzipFile(fileobj=buf, mode="rb") as gz: raw = gz.read() return pickle.loads(raw) # <-- unrestricted default unpickler ``` ```python def load(self, filename, ...): # line 365 checkpoint = torch.load(filename, ..., weights_only=True) # outer allowlist; bytes pass through intact ... if dicts_version == _DICTS_VERSION_POS: # dicts_version == 2 (current format) self.pos_dict = _unpack_pos_dict(checkpoint['dicts']) # line 378, no validation ``` **Reachable from the documented API.** `stanza.Pipeline(..., lemma…
Remediation
References
- Advisoryhttps://github.com/advisories/GHSA-gh9r-c94j-cmp5
- Referencehttps://github.com/stanfordnlp/stanza/security/advisories/GHSA-gh9r-c94j-cmp5
- Referencehttps://github.com/stanfordnlp/stanza/pull/1649
- Referencehttps://github.com/stanfordnlp/stanza/commit/b21e1c9b47b4fccff21d3b0a6c0687a19410c2f0
- Referencehttps://github.com/stanfordnlp/stanza/releases/tag/v1.15.0