You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

Unbounded bytearray(header_len) allocation in tensorizer TensorDeserializer β€” memory-exhaustion DoS loading an untrusted .tensors file

Target / scope

  • Project / repo: coreweave/tensorizer (https://github.com/coreweave/tensorizer)
  • Affected tool & version: tensorizer 2.12.1 (latest on PyPI at time of writing; pip install tensorizer==2.12.1)
  • Affected format: Tensorizer .tensors serialized tensor/model files
  • Component: tensorizer/serialization.py β†’ _TensorHeaderDeserializer.from_io()
  • Vulnerability class: CWE-789 Memory Allocation with Excessive Size Value / CWE-400 Uncontrolled Resource Consumption (Denial of Service)
  • Attack vector: Maliciously crafted .tensors file loaded by TensorDeserializer (the standard load path: TensorDeserializer(...), load_into_module, read_tensors)

SCOPE NOTE β€” please confirm before triage. This finding is in tensorizer itself, a standalone CoreWeave serialization library with its own GitHub repository and its own .tensors format. It is not a finding in modelaudit / picklescan / modelscan / fickling, and .tensors is not one of huntr's commonly-listed model formats (joblib/keras/gguf/safetensors/pickle/npy/h5/tflite/PMML/etc.). If coreweave/tensorizer is not an in-scope huntr target, this report should be redirected (e.g. reported directly to CoreWeave under their security policy) rather than triaged as a huntr bounty. I am flagging this explicitly rather than over-claiming scope.

Severity (honest)

  • Impact: Denial of Service only. No code execution, no file read/write, no information disclosure. The allocation is driven before any tensor data is interpreted, so it does not yield an OOB read/write primitive β€” it is purely resource exhaustion.
  • Realistic dollar tier: DoS, low tier (β‰ˆ up to $1.5k equivalent) under the standard ladder (RCE > file-access > DoS). .tensors is not in the $4k format set, and even if it were, this is a pure DoS, not RCE/file-access. Treat as a Low/Medium report. I am deliberately not inflating this to a high-value class.
  • CVSS-ish framing: AV:N (file is typically fetched from a remote/HTTP/S3/Redis source β€” tensorizer's primary advertised use case) / AC:L / PR:N / UI:R (a load must be triggered) / availability-only. Roughly CVSS 6.5 (Medium) if the loader runs in a service that ingests third-party models; lower if loads are always operator-initiated on trusted files.

Summary

TensorDeserializer parses each tensor's on-disk header by first reading an 8-byte little-endian uint64 length prefix straight out of the file, then eagerly allocating a bytearray of exactly that many bytes β€” before validating it against the bytes actually remaining in the stream, against the file size, or against any sane upper bound. An attacker who controls the .tensors file sets this header_len to an arbitrary 64-bit value. A file of only a few hundred KB can therefore force:

  • an instant MemoryError (e.g. header_len = 2**47 = 128 TiB β€” the allocation is refused), crashing the loading process; or
  • a real multi-gigabyte commit (e.g. header_len = 8 GiB) that is allocated and zero-filled, with an amplification factor of ~33,000x (256 KB file β†’ 8 GiB RSS). Tuned to just under host RAM, this drives the machine into swap-thrash / the Linux OOM killer.

Because tensorizer explicitly markets .tensors as a fast, safe alternative to pickle that loads models directly "from HTTP/HTTPS, Redis, and S3 endpoints" (per its own package README), a malicious file pulled from an untrusted model source crashing or OOM-killing the loader is a realistic availability attack.

Root cause

tensorizer/serialization.py, _TensorHeaderDeserializer.from_io() (line numbers from the 2.12.1 wheel):

604:    header_len_segment: ClassVar[struct.Struct] = struct.Struct("<Q")
...
641:        header_len_bytes = reader.read(cls.header_len_segment.size)
642:        offset = cls.header_len_segment.size
643:        header_len: int = cls.header_len_segment.unpack(header_len_bytes)[0]   # attacker-controlled uint64 from FILE
644:        if header_len == 0:
645:            return None
646:        buffer = bytearray(header_len)         # <-- eager, UNBOUNDED allocation
647:        buffer[:offset] = header_len_bytes
648:        with memoryview(buffer) as mv:
649:            reader.readinto(mv[offset:])       # short read silently leaves the rest zero-filled

header_len comes directly from untrusted file bytes. There is:

  • no comparison to the remaining stream length / file size,
  • no maximum-header-size constant,
  • no incremental/chunked read.

bytearray(header_len) commits and zeroes the full requested size up front. The subsequent readinto does not protect anything: if the file is short, it simply does a partial read and leaves the remainder of the giant buffer zeroed (so a small file with a large header_len still costs the full allocation).

The same unbounded-from-uint64 pattern is reachable on the normal load path: TensorDeserializer.__init__ β†’ header iteration β†’ _TensorHeaderDeserializer.from_io() for each tensor described in the file metadata.

Proof of concept

Build: start from a valid single-tensor file produced by the real TensorSerializer, then surgically overwrite only the per-tensor header's 8-byte <Q length prefix. (The PoC locates that prefix via the metadata QQQ header_offset field β€” no guessing.) Everything else in the file stays valid, so the file is accepted and parsed as normal until the allocation.

Assertion: loading the crafted file with a normal TensorDeserializer(path, device="cpu") and iterating its keys must either raise MemoryError or commit multi-GB RSS from a sub-MB file.

Captured output (reproduce.py, tensorizer 2.12.1, Python 3.12.10, 16 GB RAM host):

tensorizer 2.12.1
python 3.12.10
========================================================================
[marker] wrote benign marker: .../Temp/tensorizer_dos_marker.txt
------------------------------------------------------------------------
[base  ] unmodified valid.tensors loads in 0.001s (sanity OK)
------------------------------------------------------------------------
[craft ] evil_hdrlen_128tib.tensors: size=262315B  header_len 74 -> 140737488355328 (0x800000000000)
[load 1] header_len=2**47 (128 TiB)  ->  MemoryError in 0.03s
------------------------------------------------------------------------
[craft ] evil_hdrlen_8gib.tensors: size=262315B  header_len 74 -> 8589934592 (0x200000000)
[load 2] header_len=8 GiB (256 KB file)  ->  completed in 7.03s, RSS +8590 MB
------------------------------------------------------------------------
RESULT: DoS CONFIRMED
  128 TiB header_len -> MemoryError in 0.03s (allocation refused)
  8 GiB   header_len -> committed +8590 MB RSS from a 256 KB file

Interpretation:

  • Case 1 (header_len = 2**47): a 256 KB file makes the loader request 128 TiB; CPython refuses and raises MemoryError, terminating the load. Unconditional crash, no large RAM needed on the victim.
  • Case 2 (header_len = 8 GiB): the same 256 KB file makes the loader actually commit and zero-fill 8 GiB (+8590 MB RSS measured) in ~7 s. Amplification β‰ˆ 33,000x. Set just below victim RAM, this thrashes / triggers the OOM killer.

Artifacts in this directory:

  • reproduce.py β€” self-contained, portable (Linux/Windows via tempfile.gettempdir()) reviewer script. Writes a benign /tmp marker, builds the valid file, crafts both malicious files, loads them, and asserts the DoS.
  • poc_header_len_dos.py β€” the original development PoC (same logic, includes a clean baseline + RSS instrumentation).
  • evil_hdrlen_128tib.tensors, evil_hdrlen_8gib.tensors β€” pre-crafted malicious files (256 KB each). Load either with TensorDeserializer to reproduce directly without rebuilding.

Impact / threat model

tensorizer's stated purpose is fast model loading from remote, potentially untrusted sources (HTTP/HTTPS, S3, Redis), and it is positioned as a safe pickle alternative. Realistic scenarios:

  • A model-serving / inference platform (e.g. an autoscaling vLLM-style fleet, which is exactly tensorizer's design target) pulls a .tensors artifact by name/URL from object storage or a model hub. A single poisoned artifact OOM-kills the worker on load β€” repeatable, cheap (the malicious file is tiny), and pre-authentication from the worker's perspective.
  • A multi-tenant or community model registry serving .tensors files: one malicious upload crashes every consumer that loads it.
  • CI / batch pipelines that deserialize third-party .tensors checkpoints.

Severity is bounded to availability: no RCE, no data exfiltration. But it is a reliable, low-cost crash/OOM of any process that loads attacker-influenced .tensors data, which contradicts the format's "safe to load untrusted" positioning.

Suggested remediation

Bound header_len before allocating, and avoid committing the full buffer up front:

  1. Sanity cap: reject header_len larger than a sane maximum header size (headers are tiny β€” a few KB; even a generous cap of, say, a few MB is far above any legitimate header). Raise a clear deserialization error instead of allocating.
  2. Bound against the stream: when the total file size is known (the file/seekable stream case, which is the common path), require header_offset + header_len <= file_size before allocating.
  3. Defensive read: read the header incrementally / readinto against a right-sized buffer derived from validated bounds, rather than bytearray(attacker_uint64).

Example minimal fix:

MAX_HEADER_LEN = 64 * 1024 * 1024  # generous upper bound; real headers are KB-scale
header_len = cls.header_len_segment.unpack(header_len_bytes)[0]
if header_len == 0:
    return None
if header_len < cls.header_len_segment.size or header_len > MAX_HEADER_LEN:
    raise ValueError(f"corrupt or malicious .tensors header_len: {header_len}")
buffer = bytearray(header_len)

(The same bounding discipline should be applied to any other length/size field in the header and metadata that is read as a width-N integer and used to size an allocation or a read.)

Dedupe / prior-art note (honest)

  • I could not find a published CVE / GHSA / huntr report describing this specific unbounded bytearray(header_len) allocation in tensorizer. If CoreWeave or huntr has an existing internal/duplicate report for unbounded header/length allocation in .tensors parsing, treat this as a duplicate.
  • This is the generic "trust a length field from an untrusted file, allocate it eagerly" bug class (CWE-789). The novelty claim is only that it is present and weaponizable in tensorizer 2.12.1's .tensors header parser on the default load path β€” not that the bug class is new.
  • See the SCOPE NOTE above: confirm coreweave/tensorizer is an in-scope target before triaging as a paid huntr finding; otherwise this belongs upstream with CoreWeave directly.
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support