Hironabe333's picture
Upload README.md with huggingface_hub
e90f4c4 verified
|
Raw
History Blame Contribute Delete
5.79 kB
# pypmml RegressionModel normalizationMethod Authority Gap — PoC
**Vulnerability class**: Structural Invariant / Authority Gap — Score Normalization Function Substitution
**Target library**: pypmml (Python PMML consumer, JVM-backed via jpmml)
**PMML format**: `.pmml` (PMML 4.4, RegressionModel)
**Severity**: Medium
**Submission platform**: huntr MFV
---
## Summary
The `normalizationMethod` attribute on a PMML `RegressionModel` element controls how raw regression scores are transformed into output probabilities. pypmml executes this attribute verbatim from the PMML XML but does **not** expose it via its public Model API.
A consumer loading a PMML model via pypmml:
- Cannot detect which normalization function was applied
- Receives no warning when the normalization produces mathematically invalid outputs
- Receives no exception when the normalization produces silent NaN predictions
### Two distinct failure modes
| Variant | `normalizationMethod` | Effect |
|---|---|---|
| `mutant_none.pmml` | `none` | Raw regression scores returned as probabilities — values such as `3.5`, `-2.8`, `-4.0` are returned for a 3-class model. No exception, no warning. |
| `mutant_logit.pmml` | `logit` | All predictions return `label=None`, `prob=NaN` for every input. No exception, no warning. |
| `baseline.pmml` | `softmax` | Valid probabilities in `[0, 1]`, sum ≈ 1. Correct behavior. |
### API authority gap
```python
from pypmml import Model
model = Model.fromFile("mutant_none.pmml")
model.normalizationMethod # → AttributeError: 'Model' object has no attribute 'normalizationMethod'
```
The `normalizationMethod` attribute is not in pypmml's public API surface (`algorithmName`, `classes`, `dataDictionary`, `functionName`, `header`, `inputFields`, `inputNames`, `modelElement`, `modelName`, `outputFields`, `outputNames`, `predict`, `targetField`). A downstream consumer has no programmatic way to detect which normalization function is being applied.
---
## Repository Contents
| File | Description |
|---|---|
| `baseline.pmml` | 3-class RegressionModel, `normalizationMethod=softmax` — valid probabilities |
| `mutant_none.pmml` | Same model, `normalizationMethod=none` — invalid probability range |
| `mutant_logit.pmml` | Same model, `normalizationMethod=logit` — silent NaN predictions |
| `reproduce.py` | Loads all 3 variants, verifies failure modes, outputs `runtime_results.json` |
| `inspect_artifacts.py` | Verifies structural identity (only `normalizationMethod` differs), outputs `hash_matrix.json` |
| `SHA256SUMS.txt` | SHA-256 hashes for all files |
---
## Reproduction
**Requirements**: Python 3.8+, Java 8+ (for pypmml JVM backend)
```bash
pip install pypmml
python reproduce.py
python inspect_artifacts.py
```
### Expected output — `reproduce.py`
```
=== baseline (baseline.pmml) ===
{x1:2.0, x2:0.5} -> label=A, A=0.9626, B=0.0083, C=0.0291 [valid]
...
warnings_emitted: []
normalizationMethod API: AttributeError
=== mutant_none (mutant_none.pmml) ===
{x1:2.0, x2:0.5} -> label=A, A=3.5, B=-1.25, C=-1.25 [INVALID_RANGE]
{x1:0.1, x2:3.0} -> label=B, A=-2.8, B=1.9, C=1.9 [INVALID_RANGE]
{x1:-1.0, x2:2.0} -> label=B, A=-4.0, B=2.5, C=2.5 [INVALID_RANGE]
warnings_emitted: []
normalizationMethod API: AttributeError
=== mutant_logit (mutant_logit.pmml) ===
ALL inputs -> label=None, A=nan, B=nan, C=nan [SILENT_NAN]
warnings_emitted: []
normalizationMethod API: AttributeError
[PASS] A1: baseline softmax: all predictions valid
[PASS] A2: mutant_none: at least one prediction has prob out of [0,1]
[PASS] A3: mutant_logit: all predictions silent NaN
[PASS] A4: no warnings emitted for any variant
[PASS] A5: normalizationMethod not accessible via public API
Overall: ALL_PASS
```
---
## Model Structure
All three PMML files are structurally identical. The only difference is the `normalizationMethod` attribute:
```xml
<!-- baseline.pmml -->
<RegressionModel functionName="classification" normalizationMethod="softmax" targetFieldName="label">
<!-- mutant_none.pmml -->
<RegressionModel functionName="classification" normalizationMethod="none" targetFieldName="label">
<!-- mutant_logit.pmml -->
<RegressionModel functionName="classification" normalizationMethod="logit" targetFieldName="label">
```
The model uses 3 target classes (A, B, C), 2 numeric predictors (x1, x2), and 3 RegressionTables with fixed coefficients. The 3-class structure is required to reproduce the `none`→invalid-range and `logit`→NaN behaviors; pypmml handles binary 2-class normalization differently.
---
## Impact
An attacker who can supply a malicious PMML model file can set `normalizationMethod` to cause:
1. **Invalid probability outputs** (`none`): downstream ML pipelines treating outputs as probabilities receive values outside `[0, 1]`. Downstream probability-based decisions (thresholding, calibration, ensemble weighting) silently operate on mathematically invalid inputs.
2. **Silent NaN propagation** (`logit`): all predictions return `None`/`NaN` with no exception and no warning. Downstream code checking `predicted_label` receives `None`; probability outputs propagate `NaN` through any further computation.
3. **Uninspectable attack surface**: pypmml's public API does not expose `normalizationMethod`. A consuming application cannot detect which normalization was applied and cannot validate that probability outputs are mathematically valid.
---
## Dataset Access
This repository is gated (manual review required). To request access, submit a request through the Hugging Face interface.
---
## License
This repository contains proof-of-concept material for a bug bounty submission. All PMML files and scripts are provided solely for security research purposes.