EnigmaConsultant's picture
Upload folder using huggingface_hub
ea7f8d0 verified
|
Raw
History Blame Contribute Delete
7.03 kB
metadata
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:

# 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)

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

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