| --- |
| license: mit |
| --- |
| |
| # tensorizer `.tensors` deserializer β DoS PoC (unbounded allocation + integer overflow) |
|
|
| Proof-of-concept malicious model files for two denial-of-service flaws in the |
| [`tensorizer`](https://pypi.org/project/tensorizer/) `.tensors` binary deserializer |
| (`tensorizer==2.12.1`, the CoreWeave/vLLM fast model-load format). |
|
|
| Both fire at **`TensorDeserializer(...)` construction** (default `lazy_load=False`), i.e. merely |
| opening an attacker-supplied `.tensors` file β no `.load()` call required. |
|
|
| ## Affected |
| - Package: `tensorizer==2.12.1` (PyPI latest, 2026-06-20); HEAD `652d3c016c84836bba97153e108821c11428ac40`. |
| - File: `tensorizer/serialization.py`. |
|
|
| ## Reproduce |
|
|
| ```bash |
| python -m venv venv && . venv/bin/activate |
| pip install tensorizer==2.12.1 |
| |
| # Finding A β unbounded allocation (CWE-789): 73-byte file declares a 256 TiB metadata blob |
| python -c "import tensorizer; tensorizer.TensorDeserializer('poc_a_meta_281tib.tensors')" |
| # -> MemoryError at tensorizer/serialization.py:841 (encoded_metadata = reader.read(total_len)) |
| |
| # Finding B β numpy.prod int64 silent overflow (CWE-190): meta-tensor shape wraps the declared size |
| python -c "import tensorizer; tensorizer.TensorDeserializer('poc_b_neg_assert.tensors')" |
| # -> AssertionError at tensorizer/serialization.py:3137 (assert is_meta or needed_buffer_size == header.data_length) |
| |
| # Control β a well-formed file loads cleanly (1 tensor) |
| python -c "import tensorizer; print(len(list(tensorizer.TensorDeserializer('valid.tensors').keys())), 'tensor')" |
| ``` |
|
|
| ## Files |
| | File | Bytes | Trigger | |
| |--|--|--| |
| | `poc_a_meta_281tib.tensors` | 73 | Finding A β metadata `total_len` = `0xFFFFFFFFFFFF` (256 TiB) β `MemoryError` @ `serialization.py:841` (main thread, no `try`). Amplification β 3.86e12Γ. | |
| | `poc_a_header_1gib.tensors` | 262 KB | Finding A β per-tensor `header_len` = 1 GiB β `bytearray(header_len)` allocates 1 GiB with no bound (process RSS 176 β 1200 MiB), proving the missing length check. | |
| | `poc_b_neg_assert.tensors` | 262 KB | Finding B β v5 long-shape meta-tensor, `numpy.prod((2**62, 2))` wraps int64 to `-2**63` β `AssertionError` @ `serialization.py:3137`. | |
| | `valid.tensors` | 262 KB | Control β well-formed single-tensor file (loads clean). | |
| | `generate_poc_a.py`, `generate_poc_b.py` | β | Scripts that synthesize the files from a valid baseline. | |
|
|
| ## Root cause (summary) |
| - **A:** file-declared length prefixes (`_MetadataDeserializer.from_io` `total_len` @ `:841`; `_TensorHeaderDeserializer.from_io` `header_len` @ `:646`) drive allocations with no upper bound and no check against remaining file size. |
| - **B:** `TensorEntry.deserialized_length` (`:231`) computes `num_elements = numpy.prod(self.shape)` in fixed-width int64, which silently wraps; the wrapped size is summed at `:1964` and reused as the buffer size, defeating the `:3137` assert. |
|
|
| Memory-corruption was investigated and **refuted** β numpy's `PyArray_NewFromDescr` performs overflow-safe size |
| arithmetic and a hard buffer-length check, so the impact is process-termination DoS, not OOB read/write. |
|
|
| ## Proposed fix |
| - **A:** bound each file-declared length against the remaining input size (or a configurable cap) before allocating. |
| - **B:** compute the element count with arbitrary-precision Python ints (`math.prod`) and explicitly overflow-check before allocating. |
|
|