EnigmaConsultant's picture
Upload folder using huggingface_hub
75eaf21 verified
|
Raw
History Blame Contribute Delete
8.83 kB
# sklearn-pmml-model: Incorrect SparseArray Indices/Entries pairing in `parse_sparse_array()` -> IndexError DoS or silent model-output corruption
**Target repo:** https://github.com/iamDecode/sklearn-pmml-model
**Version confirmed:** v1.0.8 (PyPI), verified byte-identical to current GitHub `master` `sklearn_pmml_model/base.py` via `diff` against a freshly fetched copy of `raw.githubusercontent.com`.
**CWE:** CWE-129 (Improper Validation of Array Index) / incorrect array index usage.
**Reported:** 2026-07-12
## Summary
`parse_sparse_array()` in `sklearn_pmml_model/base.py` mis-pairs the `<Indices>`
and `<Entries>` elements of a PMML `<SparseArray>`. Per the PMML/DMG spec,
position *i* of `<Indices>` names the destination slot for position *i* of
`<Entries>` (i.e. the correct code is `values[indices[i]] = entries[i]`, e.g.
via `zip`/`enumerate`). The actual code instead reuses the **value** of each
index as if it were also a valid position into `entries`:
```python
for index in indices:
values[index] = entries[index]
```
This is wrong for essentially any non-trivial sparse array (any case where
the index values are not exactly the sequence `0,1,2,...,len(entries)-1`),
which is the entire point of using a `SparseArray` instead of a dense
`Array` in the first place.
This bug is reached through the library's own public, documented API
(`sklearn_pmml_model.auto_detect.auto_detect_estimator()`, and the
`PMMLSVR`/`PMMLSVC` classes that use it), the same call path as an
already-filed, separate SparseArray `@n` uncontrolled-allocation finding.
**This is a distinct defect**: different root cause (indexing/pairing logic,
not pre-allocation size), different line (`base.py` ~line 395 vs. the `@n`
allocation site), and **not** fixed by that finding's suggested remediation
(bounding/capping `@n`) β€” this bug fires or silently corrupts data
regardless of how small `@n` is.
## Root cause
File: `sklearn_pmml_model/base.py`, function `parse_sparse_array()`
(approx. lines 368-396 of current master):
```python
values = [0] * int(array.get('n'))
indices = [int(i) - 1 for i in array.find('Indices').text.split(' ')]
...
entries = [float(x) for x in element.text.split(' ')] # (or int, per type)
...
for index in indices:
values[index] = entries[index]
```
`indices` holds the 0-based (converted from PMML's 1-based) *target
positions* named by `<Indices>`. `entries` is a separate, independently
sized list parsed from `<Entries>` / `<NUM-Entries>` / `<REAL-Entries>` /
`<INT-Entries>`. The code indexes `entries` using the *index value itself*
rather than the *position within the indices list*, so:
- If any index value is `>= len(entries)`, `entries[index]` raises an
uncaught `IndexError`.
- If index values are within range of `len(entries)` but not exactly
`0, 1, 2, ..., len(entries)-1` in order, the wrong entries silently land
in the wrong slots β€” no exception, but corrupted values.
Two security-relevant, independently reachable outcomes:
1. **IndexError crash / DoS** β€” whenever any index value is
`>= len(entries)` (the normal case for genuinely sparse data: a large
feature space with few non-zero entries), parsing raises an uncaught
`IndexError` and aborts the caller. No huge-allocation trick needed β€”
`n` can be a completely ordinary, small value.
2. **Silent model-output corruption** β€” whenever index values happen to
stay in range but are not in strictly sequential order, the resulting
support-vector (or coefficient) values are silently wrong. An
attacker-crafted PMML file loads "successfully" but yields corrupted
predictions at inference time, undermining the integrity of the loaded
model with no visible error.
## Proof of Concept
Three minimal (~1KB) `SupportVectorMachineModel` PMML files, each with a
single support vector encoded as a `<SparseArray>`, loaded via the
library's public, documented API,
`sklearn_pmml_model.auto_detect.auto_detect_estimator()`.
### 1. `wrong_indices.pmml` β€” IndexError DoS
```xml
<SparseArray type="real" n="5">
<Indices>1 3 5</Indices>
<Entries>10 20 30</Entries>
</SparseArray>
```
Real, captured output loading this file:
```
Traceback (most recent call last):
File "<string>", line 6, in <module>
clf = auto_detect_estimator('/home/kali/hunt-workspace/pmml-index-bug/wrong_indices.pmml')
File "/home/kali/pmml-verify-build/sklearn-pmml-model/sklearn_pmml_model/auto_detect/base.py", line 40, in auto_detect_estimator
return auto_detect_regressor(pmml, **kwargs)
File "/home/kali/pmml-verify-build/sklearn-pmml-model/sklearn_pmml_model/auto_detect/base.py", line 129, in auto_detect_regressor
return parse(f)
File "/home/kali/pmml-verify-build/sklearn-pmml-model/sklearn_pmml_model/auto_detect/base.py", line 123, in parse
return reg(pmml, **kwargs)
File "/home/kali/pmml-verify-build/sklearn-pmml-model/sklearn_pmml_model/svm/_classes.py", line 295, in __init__
PMMLBaseSVM.__init__(self)
File "/home/kali/pmml-verify-build/sklearn-pmml-model/sklearn_pmml_model/svm/_base.py", line 56, in __init__
get_vectors(vector_dictionary, s) for s in self.support_
File "/home/kali/pmml-verify-build/sklearn-pmml-model/sklearn_pmml_model/svm/_base.py", line 109, in get_vectors
return np.array(parse_array(array))
File "/home/kali/pmml-verify-build/sklearn-pmml-model/sklearn_pmml_model/base.py", line 330, in parse_array
return parse_sparse_array(array)
File "/home/kali/pmml-verify-build/sklearn-pmml-model/sklearn_pmml_model/base.py", line 395, in parse_sparse_array
values[index] = entries[index]
IndexError: list index out of range
Exception: <class 'IndexError'> list index out of range
```
### 2. `silent_corruption.pmml` β€” silent model-output corruption
```xml
<SparseArray type="real" n="3">
<Indices>2 3 1</Indices>
<Entries>10 20 30</Entries>
</SparseArray>
```
Real, captured output loading this file:
```
Loaded OK. support_vectors_ = [[10. 20. 30.]]
Expected per PMML spec (Indices 1-based positional pairing with Entries): [[30, 10, 20]]
```
No exception is raised, but the loaded support-vector values are wrong,
silently corrupting any predictions made with this model.
### 3. `sequential_control.pmml` β€” negative control
```xml
<SparseArray type="real" n="3">
<Indices>1 2 3</Indices>
<Entries>10 20 30</Entries>
</SparseArray>
```
Real, captured output loading this file:
```
Negative control (sequential Indices=1,2,3): support_vectors_ = [[10. 20. 30.]] -- correct, no bug visible here
```
This case loads correctly, because when the index *value* happens to equal
its *position*, the buggy `entries[index]` lookup accidentally coincides
with the correct `entries[position]` lookup. This is why ordinary/unit-test
PMML fixtures β€” which tend to use sequential toy data β€” would not have
surfaced this bug.
## Verification environment
- Real execution against a fresh checkout of
`iamDecode/sklearn-pmml-model` (v1.0.8) at
`/home/kali/pmml-verify-build/sklearn-pmml-model`.
- Confirmed byte-identical to current GitHub `master`'s
`sklearn_pmml_model/base.py` via `diff` against a freshly fetched copy
from `raw.githubusercontent.com` (included here as `latest_base.py` for
identity confirmation).
- All three PoC files loaded through the library's own public,
documented API entry point, `auto_detect_estimator()` β€” no internal/private
functions called directly.
## Dedup check
GitHub code/issue search API against `iamDecode/sklearn-pmml-model` for
`"SparseArray"` and `"IndexError"` both returned `total_count: 0`.
`repos/.../security-advisories` returned `[]`. Checked 2026-07-12.
This finding is a **distinct** defect from an already-filed SparseArray `@n`
uncontrolled-allocation finding against the same project: different root
cause (Indices/Entries pairing logic vs. unbounded pre-allocation of the
`values` list), different line, and not remediated by that finding's fix
(bounding `@n`) since this bug is independent of the size of `@n`.
## Suggested fix
Pair `indices` and `entries` positionally, per the PMML spec, e.g.:
```python
for pos, index in enumerate(indices):
values[index] = entries[pos]
```
(equivalently, `for index, entry in zip(indices, entries): values[index] = entry`),
and validate `len(indices) == len(entries)` and that every index is within
bounds of `values`, raising a clear `ValueError`/`Exception` rather than an
uncaught `IndexError` for malformed input.
## Files in this repo
- `wrong_indices.pmml` β€” PoC 1 (IndexError DoS)
- `silent_corruption.pmml` β€” PoC 2 (silent corruption)
- `sequential_control.pmml` β€” PoC 3 (negative control)
- `latest_base.py` β€” freshly fetched copy of current GitHub master
`sklearn_pmml_model/base.py`, used to confirm the verified build matches
the currently released code.