ndarray-npy (jturner314) CWE-770/789 unbounded allocation PoC
Proof-of-concept file for a huntr Model Format Vulnerability report against
jturner314/ndarray-npy (Rust crate ndarray-npy, 10.3M+ downloads on
crates.io - independent NPY/NPZ implementation for the ndarray ecosystem,
not the official NumPy/Python project).
evil.npy is a 12-byte file: the NPY magic (\x93NUMPY), version 2.0,
and a 4-byte little-endian header_len field set to 4000000000 (~3.7GB) -
with no header dict data following.
Header::from_reader (header.rs:458) does
let mut buf = vec![0; header_len]; - an unconditional allocation of
header_len bytes - before reading the header dict itself. For NPY versions
2.0/3.0, header_len is a u32 (up to ~4.29GB) with no upper-bound check
(the usize::try_from nearby is only a portability check, not a size
limit) - this allocation happens at the very start of parsing, before the
shape/dtype are even read.
This repo's file targets header_len specifically because it is reached
earliest and has no overflow-guard at all (unlike the separate shape-based
len allocation in num.rs, which at least passes through a
checked_mul overflow guard before reaching the same class of allocate-
before-read primitive).
Not runtime-executed on this host (no cargo/rustc toolchain available,
consistent with the underlying finding's own documented limitation) -
confidence rests on vec![0; n]'s documented, unconditional Rust semantics
(immediate allocation request for exactly n elements, not lazy) and on an
independent blind-adversarial review that reached the same conclusion on a
separate clone.
Reachable via the public API: ndarray_npy::read_npy(path) /
ArrayBase::read_npy(reader), and via NpzReader::by_name/by_index for
.npz archives (both ultimately call the same read_npy path).
Impact: Denial of Service - an attacker who can supply or influence an
untrusted .npy/.npz file can force unbounded memory allocation
(up to ~4.29GB) from a file as small as 12 bytes.