Instructions to use MBM7/skops-dtype-decompression-bomb-poc with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Scikit-learn
How to use MBM7/skops-dtype-decompression-bomb-poc with Scikit-learn:
# β οΈ Model filename not specified in config.json
- Notebooks
- Google Colab
- Kaggle
| tags: | |
| - security | |
| - vulnerability | |
| - poc | |
| - skops | |
| - numpy | |
| - sklearn | |
| - huggingface | |
| - decompression-bomb | |
| - cwe-789 | |
| license: mit | |
| # skops β .skops NPY Dtype Decompression Bomb (PoC) | |
| **Repo:** `MBM7/skops-dtype-decompression-bomb-poc` | |
| **Status:** Responsible disclosure β submitted to Huntr | |
| **Severity:** High / CWE-789 | |
| **Package:** `skops` (PyPI) β HuggingFace official sklearn model format | |
| --- | |
| ## Summary | |
| A crafted **8.9 KB** `.skops` sklearn model file causes `skops.io.loads()` | |
| to allocate **2 GB of memory** before failing. | |
| | File size | Claimed dtype itemsize | Peak allocation | Amplification | | |
| |-----------|------------------------|-----------------|---------------| | |
| | 8,935 bytes | 500,000,000 | **500 MB** | 1 : 55,959 | | |
| | 8,935 bytes | 2,000,000,000 | **2,000 MB** | 1 : 223,838 | | |
| --- | |
| ## Root Cause | |
| `skops/io/_numpy.py`, `NdArrayNode._construct()`: | |
| ```python | |
| content = np.load( | |
| self.children["content"], # BytesIO of .npy file from .skops ZIP | |
| allow_pickle=False | |
| ) # β np.load allocates count * dtype.itemsize BEFORE reading data | |
| ``` | |
| The `.skops` format is a ZIP archive containing `schema.json` + `.npy` files. | |
| When loading, `NdArrayNode` reads each `.npy` file into a `BytesIO` and passes | |
| it directly to `np.load()` β **with no validation of dtype.itemsize or shape**. | |
| A crafted `.npy` file inside the ZIP claiming `dtype=|S2000000000` (2 GB | |
| itemsize) causes `np.load()` to pre-allocate 2 GB before attempting to | |
| read data. | |
| --- | |
| ## Attack | |
| Replace any `.npy` file inside a legitimate `.skops` model archive with a | |
| dtype bomb `.npy` header. `schema.json` remains unchanged β skops trusts it | |
| and loads all `.npy` files unconditionally: | |
| ```python | |
| import skops.io as sio | |
| # Load triggers NdArrayNode._construct() β np.load() β 2GB allocation | |
| trusted = sio.get_untrusted_types(data=bomb_skops_bytes) | |
| model = sio.loads(bomb_skops_bytes, trusted=trusted) | |
| ``` | |
| --- | |
| ## Distinct from numpy NPY finding | |
| | | numpy NPY | **skops** | | |
| |--|--|--| | |
| | Entry point | `np.load('file.npy')` | `skops.io.loads(skops_bytes)` | | |
| | Format | `.npy` raw | `.skops` ZIP (sklearn model) | | |
| | Ecosystem | NumPy arrays | HuggingFace sklearn model sharing | | |
| | Context | Direct array loading | sklearn model deserialization | | |
| --- | |
| ## Reproduce | |
| ```bash | |
| pip install skops scikit-learn numpy | |
| python poc_skops_dtype_bomb.py | |
| ``` | |
| Expected: | |
| ``` | |
| .skops file size : 8,935 bytes | |
| Expected allocation: 2,000,000,000 bytes | |
| Amplification : 1:223,838 | |
| Result : ValueError: EOF: reading array data... | |
| Peak memory : 2000 MB β allocated before error | |
| ``` | |
| --- | |
| ## Impact | |
| Any service loading user-supplied `.skops` files from HuggingFace Hub or | |
| other sources can be OOM-killed by a sub-10KB payload. skops is the | |
| **official HuggingFace format** for sharing sklearn models β directly | |
| relevant to the AI/ML supply chain. | |
| --- | |
| ## Suggested Fix | |
| In `NdArrayNode._construct()`, validate before `np.load()`: | |
| ```python | |
| MAX_ARRAY_BYTES = 512 * 1024 * 1024 # 512 MB | |
| buf = self.children["content"] | |
| # Peek at NPY header without allocating | |
| hdr = np.lib.format.read_array_header_1_0(buf) | |
| buf.seek(0) | |
| if hdr[1].itemsize > MAX_ARRAY_BYTES: | |
| raise ValueError( | |
| f"NPY dtype itemsize {hdr[1].itemsize} exceeds limit β " | |
| "possible decompression bomb" | |
| ) | |
| content = np.load(buf, allow_pickle=False) | |
| ``` | |
| --- | |
| ## Environment | |
| | Package | Version | | |
| |---------|---------| | |
| | skops | 0.14.0 | | |
| | numpy | 2.4.4 | | |
| | Python | 3.12 | | |
| --- | |
| *skops was created as a **safer alternative to pickle** for sklearn models.* | |
| *Ironic: the safety-focused format has an unvalidated allocation path.* | |