safetensors-cpp (syoyo) heap-buffer-overflow READ in csafetensors_get_tensor_data PoC
Proof-of-concept for a huntr Model Format Vulnerability report against
syoyo/safetensors-cpp - specifically the newer C11 API (csafetensors.c/.h,
commit af90b6c, 2025-12-28), not the older C++ header (safetensors.hh).
evil.safetensors is a 75-byte file whose header declares a tensor evil
with dtype=F32, shape=[1000000] (needs 4,000,000 real bytes) but
data_offsets=[0,4] (only 4 real bytes present in the file).
csafetensors_get_tensor_data() (csafetensors.c:1203-1221) checks ONLY
tensor->data_offset_begin > max_size - it does not check data_offset_end
and does not compare against shape/dtype, returning a bare pointer with
no length. csafetensors_validate() (csafetensors.c:1283-1320) is the
ONLY function that actually checks data_offset_end against the buffer size
and dtype_size * shape_size against the real byte range - but it is called
only from the project's own unit tests, never from
csafetensors_load_from_file/csafetensors_load_from_memory.
Verified live (ASan, gcc, fresh clone, same commit as cited above):
$ gcc -fsanitize=address -g -O0 -std=c11 -o poc poc.c csafetensors.c -lm
$ ./poc
==ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 4
#0 in main poc.c:43
allocated by thread T0 here:
#1 in csafetensors_load_from_memory csafetensors.c:1019
Full ASan output in asan_output.txt. poc.c is the exact reproducer
compiled and run.
Why this is a meaningfully undertested path (not just a hypothetical)
The project's own fuzzer (fuzzer/fuzz-main.cc) only fuzzes the OLD C++
path (safetensors.hh's safetensors::load_from_memory) - it never
exercises csafetensors_* at all. The new C11 API has existed for ~7 months
with no fuzzing coverage of its own - exactly the "show us what our scanner
doesn't see" class huntr's own guidelines ask for.
Honest limits (disclosed plainly)
This is a heap out-of-bounds READ (CWE-125), not a write primitive - it does
not by itself grant code execution. Exploitability depends on whether a
downstream consumer skips calling csafetensors_validate() (which exists
and correctly catches this exact file in the project's own test suite) -
the weakness is that the convenient accessor silently does not carry the
one check it needs, not that the library is unsafe by default with no
escape hatch. The C11 API is new; no real downstream consumers of
csafetensors.c specifically (as opposed to the older safetensors.hh)
were identified.
Impact: heap-buffer-overflow read via a crafted .safetensors file with a
shape/dtype-vs-data_offsets mismatch, reachable through the standard
csafetensors_load_from_memory -> csafetensors_get_tensor_data usage
pattern shown in the project's own examples and tests.
Suggested fix: Have csafetensors_get_tensor_data() itself check
data_offset_end <= max_size (not just data_offset_begin), and/or call
csafetensors_validate() automatically inside
csafetensors_load_from_file/csafetensors_load_from_memory rather than
leaving it as an easily-skipped separate step.