MBM7's picture
Upload 2 files
950564c verified
|
Raw
History Blame Contribute Delete
4.99 kB
# coremltools MIL `fill` Operation Unbounded Allocation
**Status:** Preparing for Huntr submission
**Package:** [`coremltools`](https://pypi.org/project/coremltools/) (PyPI) β€” Apple's Core ML Tools
**File / function:** `coremltools/converters/mil/mil/ops/defs/iOS15/tensor_operation.py`, `fill.value_inference()`
**Class:** CWE-789 (Convergent Untrusted-Length Allocation)
**Severity:** High β€” an 833-byte model file causes ~2.5GB of memory allocation and 10+ seconds of CPU time, purely in Python, on any platform (no macOS or native Core ML runtime required).
## Summary
CoreML's MIL (Model Intermediate Language) `fill` operation β€” the direct equivalent of ONNX's `ConstantOfShape` β€” computes its constant-folded value with no bound check on the declared shape:
```python
# tensor_operation.py, class fill
@precondition(allow=VALUE)
def value_inference(self):
return np.full(shape=self.shape.val, fill_value=self.value.val)
```
`self.shape.val` comes from a `const` operation elsewhere in the same MIL graph β€” fully attacker-controlled in a crafted model file. There is no check on the requested element count before calling `np.full()`.
## Reaching this from a saved file
By default, `coremltools`'s conversion/save pipeline constant-folds `fill` operations into a plain `const` before writing the file, which would hide the vulnerable operation from a normally-produced `.mlpackage`. This PoC works around that by saving with an empty optimization pipeline (`pass_pipeline=ct.PassPipeline.EMPTY`), producing a **legitimate, valid `.mlpackage`** that genuinely contains an un-folded `fill` op reading its shape from a small `const`. The declared shape value is then patched directly in the saved protobuf spec (`model.mlmodel`), changing only the shape constant's 4-byte int32 value β€” nothing else in the file changes.
The patched file is loaded via `coremltools.converters.mil.frontend.milproto.load.load_mil_proto()` β€” the documented, pure-Python, platform-independent function used to reconstruct an in-memory MIL `Program` from a saved model spec. This is used by any tooling that loads, inspects, or re-converts a saved CoreML model programmatically, independent of the native macOS runtime.
## Proof of Concept
`poc_coreml_fill_bomb.py`:
```bash
pip install coremltools numpy
python poc_coreml_fill_bomb.py
```
### Result
```
=== Building legitimate .mlpackage (fill op left un-folded) ===
wrote poc_legit.mlpackage
=== Patching shape constant to 500,000,000 ===
patched model.mlmodel is 833 bytes
=== Loading patched file via load_mil_proto() (pure Python, no macOS needed) ===
-> AssertionError after 1.32s, peak RSS=2564.5 MB (allocation already happened before this error): Mismatch between var types in specification vs PyMIL
```
The `AssertionError` is an unrelated, incidental type-consistency check that happens to fire **after** the expensive allocation β€” by the time it's raised, the ~2.5GB allocation has already occurred. A more carefully crafted file (patching auxiliary type metadata to stay consistent) would very plausibly avoid tripping this check at all, since it isn't in any way related to bounding the shape value.
## Scope note (included for transparency)
`coremltools.models.MLModel(path)` β€” the top-level "just load a model" convenience API β€” does not proceed to full compilation on Linux, because it gates on `_MLModelProxy`, a compiled component only available with the native macOS Core ML runtime (`libcoremlpython`). This PoC deliberately demonstrates the vulnerability through `load_mil_proto()` instead, since that function is public, documented, pure Python, and platform-independent β€” it does not require macOS to reproduce, and represents a real, commonly-used code path (loading/inspecting/re-converting a model's MIL graph) independent of whether the native execution runtime is present.
## Impact
Any service or tool that programmatically loads or re-converts CoreML model files using `load_mil_proto()` (or code paths that reach it) β€” for example, a model conversion/validation service accepting user-uploaded models β€” can be forced into multi-gigabyte memory consumption and multi-second CPU stalls by a file under 1KB.
## Suggested fix
Before calling `np.full()` in `fill.value_inference()`, validate the requested element count (product of the declared shape) against a configurable sane maximum, and raise a clear error immediately instead of proceeding to allocate.
## Relationship to other findings
Same vulnerability class (CWE-789, CULA) and mechanism as a separately-reported finding in ONNX's `ReferenceEvaluator` `ConstantOfShape` operator (`np.full(tuple(data), value)` with an unchecked shape) β€” different library, different intermediate representation, but the same underlying missing-validation pattern recurring across model-format tooling.
## Disclosure
Please do not use this PoC against production systems you do not own or have explicit permission to test.