Tensorizer .tensors Cross-Tensor OOB Read / Uninitialized Heap Disclosure β PoC
Security research PoC. Defensive disclosure only.
This repository hosts the proof-of-concept artifact for an information-disclosure
vulnerability in coreweave/tensorizer
(TensorDeserializer), submitted via huntr.com.
- Weakness: CWE-125 (Out-of-bounds Read) + CWE-908 (Use of Uninitialized Resource)
- Affected:
tensorizer2.12.1 (latest on PyPI),tensorizer/serialization.py,TensorDeserializer._bulk_load(~lines 3135β3180) - Impact: A malicious
.tensorsfile makes the deserializer read past one tensor's real data into the adjacent tensor's header/weights and into uninitialized process heap, then returns that data as the loaded tensor.
β οΈ This PoC only demonstrates the over-read (it prints leaked floats). It contains no system-harming payload β no code execution, no file writes beyond the local working dir, no network access. The malicious file
leak2.tensorsis a benign two-tensor file whose size metadata has been patched to trigger the over-read.
What's in this repo
| File | Purpose |
|---|---|
leak2.tensors |
The malicious artifact. Loading it returns a 27-element tensor for a (declared as 4 zeros) β its own 4 zeros, then secret's header bytes, then secret's real weights. |
build_baseline.py |
Builds a clean two-tensor two.tensors baseline with TensorSerializer. |
make_leak.py |
Parses two.tensors and patches 4 fields to produce leak2.tensors. Offset-independent (it re-parses the layout). |
load_poc.py |
Loads leak2.tensors with TensorDeserializer and prints the over-read. |
leak_noO.py |
Original hard-offset version of the generator (for this exact baseline). |
Reproduce
pip install tensorizer==2.12.1 torch numpy
# Option 1: load the shipped malicious file directly
python load_poc.py
# Option 2: rebuild from scratch (deterministic)
python build_baseline.py # -> two.tensors (clean: a=4 zeros, secret=4x 1.2345)
python make_leak.py # -> leak2.tensors (patched)
python load_poc.py # -> a leaks 27 floats ending in secret's 1.2345
Expected output
a (27,) torch.float32 [0.0, 0.0, 0.0, 0.0, ... , 9.9063796e-24, 9.9063796e-24, 9.9063796e-24]
secret (4,) torch.float32 [1.2345, 1.2345, 1.2345, 1.2345]
Tensor a was serialized as 4 zeros. After loading the patched file it
returns 27 floats: its own 4 zeros, then the raw bytes of secret's header,
then secret's actual weights reinterpreted as float32. The tensor boundary has
been read straight through. verify_hash defaults to False, so this is silent.
Root cause (short)
Each tensor's size is recorded twice β once in the file metadata
(deserialized_length / data_length) and again in the per-tensor header
(data_length). Both are attacker-controlled file data, and the only guard is a
single assert needed_buffer_size == header.data_length. Setting both to the
same inflated value passes the assert; torch.empty() then allocates an
attacker-sized uninitialized buffer and file_.readinto() over-reads. Pushing
the requested size past EOF leaves the uninitialized torch.empty() tail
exposed β uninitialized process-heap disclosure.
Remediation (short)
Replace the lone assert with an always-on check; cross-check header
data_length against metadata deserialized_length and against
shape Γ dtype.itemsize; bound data_length by bytes remaining in the stream;
verify the readinto return value; and avoid torch.empty() (or zero the
unread tail) so a short read can never expose process memory.
Reported responsibly via Huntr. No third-party systems were targeted.