Views
No views yet
.tensors Cross-Tensor OOB Read / Uninitialized Heap Disclosure — PoCcoreweave/tensorizer
(TensorDeserializer), submitted via huntr.com.tensorizer 2.12.1 (latest on PyPI), tensorizer/serialization.py,
TensorDeserializer._bulk_load (~lines 3135–3180).tensors file 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 fileleak2.tensorsis a benign two-tensor file whose size metadata has been patched to trigger the over-read.
| 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). |
1pip install tensorizer==2.12.1 torch numpy
2
3# Option 1: load the shipped malicious file directly
4python load_poc.py
5
6# Option 2: rebuild from scratch (deterministic)
7python build_baseline.py # -> two.tensors (clean: a=4 zeros, secret=4x 1.2345)
8python make_leak.py # -> leak2.tensors (patched)
9python load_poc.py # -> a leaks 27 floats ending in secret's 1.2345a (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]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.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.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.