Hironabe333's picture
Upload README.md with huggingface_hub
09965c5 verified
|
Raw
History Blame Contribute Delete
6.7 kB
# ONNX ArrayFeatureExtractor Feature Selection Authority PoC
## Summary
An ONNX model file contains a runtime-consumed feature selection operator (`ai.onnx.ml.ArrayFeatureExtractor`) whose model-internal `idx` initializer controls which input feature column is selected and passed to downstream scoring. Mutating the `idx` initializer while keeping all downstream `LinearClassifier` coefficients, intercepts, and `classlabels_ints` byte-identical causes the same input tensor `[[10.0, -10.0]]` to supply a different numeric value to the scorer (`10.0` vs `-10.0`), producing different numeric scores and a flipped prediction (`label 1 → 0`).
This is pre-score feature selection authority — not class label substitution, not affine scaling, not sentinel replacement, not threshold activation, not categorical one-hot binding.
## Affected Product
- **Format:** ONNX (.onnx), operator `ai.onnx.ml.ArrayFeatureExtractor` (domain `ai.onnx.ml` v1), model-internal `idx` initializer (INT64)
- **Runtime:** onnxruntime (CPUExecutionProvider)
- **Maintainers:** Microsoft & Meta
## Vulnerability Details
The `idx` initializer consumed by `ai.onnx.ml.ArrayFeatureExtractor` directly controls which column of the input tensor reaches the downstream scorer. A mutation confined to this initializer produces a prediction change that the downstream weights (coefficients, intercepts, class labels) do not reveal.
| Field | clean.onnx | mutant.onnx |
|---|---|---|
| `ArrayFeatureExtractor` `idx` initializer | `[0]` | **`[1]`** ← only change |
| `LinearClassifier.coefficients` | `[-1.0, 1.0]` | `[-1.0, 1.0]` identical |
| `LinearClassifier.intercepts` | `[1.0, -1.0]` | `[1.0, -1.0]` identical |
| `LinearClassifier.classlabels_ints` | `[0, 1]` | `[0, 1]` identical |
Input `[[10.0, -10.0]]`:
- clean: extract index 0 → post-extract `[[10.0]]` → scores `[-9.0, 9.0]` → label **1**
- mutant: extract index 1 → post-extract `[[-10.0]]` → scores `[11.0, -11.0]` → label **0**
## Impact
A crafted ONNX model with a mutated `idx` initializer produces different inference output from an otherwise structurally identical model. Auditing tools and users examining classifier coefficients, intercepts, and output labels observe no difference — the divergence is entirely in the pre-score feature selection field. This is not a claim of RCE, ACE, or memory corruption.
## Proof of Concept
PoC repository: PLACEHOLDER
```
pip install onnx>=1.14.0 onnxruntime>=1.16.0 numpy>=1.24.0
python reproduce_onnx_feature_selection_flip.py
```
Expected output:
```
clean label: 1, scores: [-9.0, 9.0]
mutant label: 0, scores: [11.0, -11.0]
reproducibility: clean 5/5=True, mutant 5/5=True
A1: same input [[10.0,-10.0]] used -> PASS
A2: extraction index differs (clean=0, mutant=1) -> PASS
A3: downstream coefficients identical [-1.0, 1.0] -> PASS
A4: downstream intercepts identical [1.0, -1.0] -> PASS
A5: clean post-extract positive (index 0 → 10.0) -> PASS
A6: mutant post-extract negative (index 1 → -10.0) -> PASS
A7: prediction flip 1->0 (zero coeff change) -> PASS
A8: clean 5/5 repro -> PASS
A9: mutant 5/5 repro -> PASS
ONNX_ARRAY_FEATURE_EXTRACTOR_SELECTION_FLIP_CONFIRMED
```
## Runtime Evidence
| Item | Value |
|---|---|
| Input | `[[10.0, -10.0]]` (continuous numeric, same for both) |
| clean idx / post-extract / scores / label | `[0]` / `[[10.0]]` / `[-9.0, 9.0]` / **1** |
| mutant idx / post-extract / scores / label | `[1]` / `[[-10.0]]` / `[11.0, -11.0]` / **0** |
| coefficients (both) | `[-1.0, 1.0]` (byte-identical) |
| intercepts (both) | `[1.0, -1.0]` (byte-identical) |
| classlabels_ints (both) | `[0, 1]` (byte-identical) |
| Reproducibility | 5/5 |
| Assertions | 9/9 PASS |
| Hash matrix | 11/11 PASS |
| clean SHA256 | `b07ac968a6f2be7c566ea19329b0ac21fe9bb59b06f3b7773661544b8f441c8c` |
| mutant SHA256 | `1f37b3d32a76fc48a87ac9926d37044ae45d7e5e09c0dde9d69f0a32c914de14` |
## Distinctness
| Prior Finding | Root | Distinct |
|---|---|---|
| `ai.onnx.ml.Binarizer.threshold` | Threshold activation (0/1 gate per feature value) | Binarizer maps continuous→binary; ArrayFeatureExtractor selects which column reaches scorer |
| `ai.onnx.ml.Scaler.scale` | Continuous affine transform (all inputs) | Scaler.scale is not Scaler.scale; ArrayFeatureExtractor selects a column index, not a scale factor |
| `ai.onnx.ml.Imputer.imputed_value_floats` | Sentinel replacement (triggers on missing value) | Imputer fills gaps; ArrayFeatureExtractor selects which feature position feeds the scorer |
| `ai.onnx.ml.OneHotEncoder.cats_strings` | Categorical column binding | OneHotEncoder converts strings to one-hot; ArrayFeatureExtractor selects numeric column by index |
| `ai.onnx.ml.SVMClassifier.classlabels_strings` | Post-inference label rendering | Acts after scoring; ArrayFeatureExtractor acts before scoring |
| TFLite `NormalizationOptions` | FlatBuffer metadata normalization | Different format (.tflite), different runtime |
| SafeTensors `preprocessor_config.json image_mean` | HF sidecar JSON image normalization | Different format (sidecar JSON), different modality |
| Joblib `CountVectorizer.vocabulary_` | NLP token-to-column binding | Different format (.joblib), different runtime |
| SafeTensors `tokenizer.json model.vocab` | NLP token-to-ID binding | Different format (sidecar JSON), different modality |
| OpenVINO `rt_info labels` | OpenVINO IR label map | Different format (OpenVINO IR), different runtime |
| TFJS `signature.outputs` | TFJS output tensor binding | Different format (.tfjs), different runtime |
## Non-Claims
This PoC does not claim RCE, ACE, memory corruption, scanner bypass as primary impact, `classlabels_strings` label substitution, `OneHotEncoder.cats_strings` categorical binding, `Imputer.imputed_value_floats` sentinel replacement, `Scaler.scale` affine transform, `Binarizer.threshold` threshold activation, TFLite FlatBuffer normalization, or SafeTensors sidecar JSON preprocessing. Root claim: **ONNX `ai.onnx.ml.ArrayFeatureExtractor` model-internal `idx` initializer pre-score feature selection authority**.
## Recommendation
ONNX model auditing tools and consumers should validate preprocessing operator inputs and initializers (`ArrayFeatureExtractor` `idx` initializer) alongside downstream classifier weights. The `idx` initializer directly controls which input feature column is selected before downstream scoring and can flip predictions without any change to coefficients, intercepts, or class labels.
## References
- ONNX spec: ai.onnx.ml.ArrayFeatureExtractor — https://onnx.ai/onnx/operators/onnx_ml_doc_ArrayFeatureExtractor.html
- onnxruntime — https://github.com/microsoft/onnxruntime