Views
No views yet
n Attribute Unbounded Allocationsklearn-pmml-model (PyPI) — pure-Python PMML model loader
File / function: sklearn_pmml_model/base.py, parse_sparse_array()
Class: CWE-789 (Convergent Untrusted-Length Allocation)
Severity: High — a ~1.3KB model file loaded through the library's real, public, documented API consumes ~450MB of memory and takes several seconds; a larger declared value causes the process to be OOM-killed or hang indefinitely.sklearn-pmml-model parses these files using Python's stdlib xml.etree.cElementTree. When it encounters a <SparseArray> element (used for sparse-encoded data, e.g. support vector storage in SVM models), it allocates a buffer sized directly from the element's declared n attribute before validating it:1# sklearn_pmml_model/base.py
2def parse_sparse_array(array):
3 ...
4 values = [0] * int(array.get('n'))
5 indices = [int(i) - 1 for i in array.find('Indices').text.split(' ')]
6 ...array.get('n') is fully attacker-controlled in a crafted PMML file. There is no check on this value before it drives a Python list allocation — and Python lists of integers are notably memory-hungry per element (each entry is a full boxed int object plus a pointer), making this more severe than an equivalent-sized numpy buffer.xml.etree.cElementTree, for due diligence:expat enforces a built-in amplification-factor limit; a crafted DOCTYPE with nested entities raises xml.etree.ElementTree.ParseError: limit on input amplification factor (from DTD and entities) breached in well under a second.ElementTree refuses external entity references by default (reference to external entity in attribute), which is documented, long-standing safe-by-default stdlib behavior.<SparseArray>'s n attribute is trusted.poc_sklearn_pmml_sparsearray_bomb.py builds a complete, valid, minimal PMML support-vector regression model — the kind loadable via the library's own public API, sklearn_pmml_model.svm.PMMLNuSVR — containing one <REAL-SparseArray> support vector that declares n="20000000" but has only one real <Indices>/<REAL-Entries> value.1pip install sklearn-pmml-model numpy scikit-learn
2python poc_sklearn_pmml_sparsearray_bomb.pyWrote poc_sparsearray_bomb.pmml: 1266 bytes, declares SparseArray n=20,000,000 (only 1 real index/value)
Loading via the real public API: PMMLNuSVR(pmml='poc_sparsearray_bomb.pmml') ...
-> loaded, time=8.82s, peak RSS=450.8 MBn=20,000,000 so it completes in a reasonable time while still clearly demonstrating the amplification (~1:370,000, file bytes to RSS bytes). During research, n=500,000,000 in the same 1266-byte file caused the load to either hang past a 30-second timeout without finishing, or be killed outright by the OS's OOM killer, depending on available memory — see the HUGE_N constant in the script to reproduce that more severe outcome.PMMLNuSVR(pmml=path) — not an internal-function-only proof; any application that loads a PMML file with this library and does not pre-validate it is affected exactly as tested here.sklearn-pmml-model — for example, a model-serving platform or MLOps pipeline that accepts PMML models from users or third parties — can be forced into multi-hundred-megabyte-to-multi-gigabyte memory consumption, or an outright process kill, by a file of a little over a kilobyte.parse_sparse_array(), validate n against the actual number of entries found in the <Indices>/<Entries> children (or against a configurable sane maximum) before allocating [0] * n, and raise a clear error on mismatch.