EnigmaConsultant's picture
Upload folder using huggingface_hub
ea7f8d0 verified
|
Raw
History Blame Contribute Delete
7.03 kB
---
license: other
---
# sklearn-pmml-model: Uncontrolled Memory Allocation via `SparseArray/@n` (DoS)
**Target:** https://github.com/iamDecode/sklearn-pmml-model (PyPI: `sklearn-pmml-model`)
**Platform:** huntr (Model File Vulnerabilities, PMML format)
**Class:** CWE-789 (Memory Allocation with Excessive Size Value) / CWE-400 (Uncontrolled Resource Consumption)
**Status:** Gated PoC β€” access granted to protectai-bot for triage.
## Summary
`sklearn_pmml_model.base.parse_sparse_array()` builds an in-memory Python list
sized directly from the attacker-controlled `n` attribute of a `<SparseArray>`
element in a PMML file, with no upper bound or sanity check against the
document's actual content:
```python
# sklearn_pmml_model/base.py
def parse_sparse_array(array):
...
values = [0] * int(array.get('n')) # <-- unbounded allocation
indices = [int(i) - 1 for i in array.find('Indices').text.split(' ')]
...
```
Loading any PMML model that contains a `SupportVectorMachineModel` with a
`VectorInstance` whose vector is encoded as a `SparseArray` reaches this line
via `sklearn_pmml_model.svm._base.get_vectors()` -> `parse_array()`. This is
hit through the library's own public, documented API
(`auto_detect_estimator()`, `PMMLSVC`, `PMMLSVR`, `PMMLNuSVC`, `PMMLNuSVR`),
which is precisely the workflow the README recommends for loading a "trained
model exported to PMML" from any source β€” i.e., attacker-supplied model
files are the intended threat model for this library.
A ~1KB PMML file is enough to make the process attempt to allocate a
multi-terabyte Python list, which either:
- raises `MemoryError` inside `parse_sparse_array` and aborts the caller
(denial of service for that request/process), or
- (with a smaller-but-still-huge `n`) causes multi-second/minute stalls
while CPython allocates and fills the list, tying up the loading
thread/process.
There is no relationship enforced between `n` and the actual number of
`Indices`/`Entries` provided β€” the allocation happens *before* those are
even parsed, so a minimal, otherwise mostly-empty `SparseArray` triggers it.
## Attacker input -> sink chain
1. Attacker supplies a PMML file to any application that loads models via
`sklearn_pmml_model.auto_detect.auto_detect_estimator(path)` (the
library's documented top-level API) or directly via
`sklearn_pmml_model.svm.PMMLSVR/PMMLSVC(pmml=path)`.
2. `auto_detect_estimator` sees `<SupportVectorMachineModel` and target type,
and dispatches to `PMMLSVR`/`PMMLSVC`.
3. `PMMLBaseSVM.__init__` (`sklearn_pmml_model/svm/_base.py`) iterates
`VectorDictionary/VectorInstance` entries and calls
`get_vectors(vector_dictionary, s)` for each support vector id.
4. `get_vectors()` finds the vector's `SparseArray`/`REAL-SparseArray`
element and calls `parse_array(array)`.
5. `parse_array()` dispatches `SparseArray` tags to
`parse_sparse_array(array)`.
6. `parse_sparse_array()` executes
`values = [0] * int(array.get('n'))` using the raw, attacker-controlled
`n` XML attribute value with **no bound check** β€” sink reached.
## Proof of Concept
Files in this repo:
- `malicious_svr.pmml` β€” 1031-byte PMML file: a minimal `SupportVectorMachineModel`
whose single support vector is a `<SparseArray type="real" n="999999999999">`.
- `trigger_svm_api.py` β€” loads the file via `sklearn_pmml_model.svm.PMMLSVR(pmml=...)`.
- `trigger_auto_detect_api.py` β€” loads the file via the documented top-level
`sklearn_pmml_model.auto_detect.auto_detect_estimator(...)`.
### Reproduction (against an unmodified, freshly cloned/installed checkout)
```bash
git clone https://github.com/iamDecode/sklearn-pmml-model.git
python3 -m venv venv && source venv/bin/activate
pip install -e sklearn-pmml-model
# Bound virtual memory to 3GB purely so the demo fails fast/safely on the
# demonstrator's own machine; the bug itself has no dependency on any limit.
(ulimit -v 3000000; python3 trigger_auto_detect_api.py malicious_svr.pmml)
```
### Observed (real run, this environment, sklearn-pmml-model @ master, April 2026 checkout)
```
Traceback (most recent call last):
File ".../trigger_auto_detect_api.py", line 7, in <module>
clf = auto_detect_estimator(sys.argv[1])
File ".../sklearn_pmml_model/auto_detect/base.py", line 40, in auto_detect_estimator
return auto_detect_regressor(pmml, **kwargs)
File ".../sklearn_pmml_model/auto_detect/base.py", line 129, in auto_detect_regressor
return parse(f)
File ".../sklearn_pmml_model/auto_detect/base.py", line 123, in parse
return reg(pmml, **kwargs)
File ".../sklearn_pmml_model/svm/_classes.py", line 295, in __init__
PMMLBaseSVM.__init__(self)
File ".../sklearn_pmml_model/svm/_base.py", line 56, in __init__
get_vectors(vector_dictionary, s) for s in self.support_
File ".../sklearn_pmml_model/svm/_base.py", line 109, in get_vectors
return np.array(parse_array(array))
File ".../sklearn_pmml_model/base.py", line 330, in parse_array
return parse_sparse_array(array)
File ".../sklearn_pmml_model/base.py", line 369, in parse_sparse_array
values = [0] * int(array.get('n'))
MemoryError raised as expected via auto_detect_estimator(). Traceback:
```
Without an external memory limit, the same file will make the hosting
process attempt to allocate ~8TB (`999999999999 * 8 bytes` for the pointer
array alone) until the OS OOM-kills the process or the allocation fails β€”
in either case the process handling the model-load crashes.
## Impact
Any service that loads user- or third-party-supplied PMML files with
`sklearn-pmml-model` (e.g. a model-serving/inference API, an AutoML
pipeline accepting model uploads, a batch scoring job) can be crashed by
a single ~1KB file, with no valid support-vector data required. This is a
denial-of-service primitive delivered entirely through the model file β€”
the same class of "malicious model file" bug huntr's Model File
Vulnerability program targets for other formats (pickle, HDF5/Keras,
PyTorch, etc.), here affecting the PMML format via `sklearn-pmml-model`.
## Fix suggestion
Bound `n` to a sane maximum (or to the number of `Indices` actually
present) before allocating, and/or catch `MemoryError`/`OverflowError`
around PMML parsing and re-raise as a controlled `Exception`, e.g.:
```python
n = int(array.get('n'))
num_indices = len(array.find('Indices').text.split())
if n <= 0 or n > MAX_REASONABLE_SPARSE_ARRAY_SIZE or n < num_indices:
raise Exception('Invalid or excessive SparseArray size.')
values = [0] * n
```
## Dedup check
- No GitHub issues, PRs, or Security Advisories in `iamDecode/sklearn-pmml-model`
reference `SparseArray`, memory exhaustion, or denial of service
(checked 2026-07-06 via GitHub search API and the repo's Security
Advisories page β€” zero results).
- No CVE/GHSA exists for `sklearn-pmml-model` in the GitHub Advisory
Database as of 2026-07-06.
## Reporter
Enigma Partners Global β€” authorized security research, huntr Model File
Vulnerability program (target: PMML).