YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
coremltools MIL fill Operation Unbounded Allocation
Status: Preparing for Huntr submission
Package: 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:
# 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:
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.