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.

joblib.load DoS: ZeroDivisionError in read_array from attacker-controlled itemsize-0 dtype metadata

Target: joblib (PyPI package joblib) Verified version: joblib 1.5.2, numpy 2.3.5, CPython 3.13 (Kali). Vulnerable line is unchanged on joblib main. Vulnerable file/line: joblib/numpy_pickle.py, NumpyArrayWrapper.read_array, line 191. Impact: Denial of service (unhandled ZeroDivisionError) on joblib.load() of an untrusted .joblib file. Not memory corruption or RCE. Class: CWE-369 (Divide By Zero) reachable from untrusted deserialization metadata.


Root cause

In joblib/numpy_pickle.py, NumpyArrayWrapper.read_array computes a read-chunk count by dividing the buffer size by the dtype item size, with no guard against a zero itemsize:

# joblib/numpy_pickle.py  (line ~191, NumpyArrayWrapper.read_array)
max_read_count = BUFFER_SIZE // min(BUFFER_SIZE, self.dtype.itemsize)

self.dtype is attacker-controlled metadata carried inside the pickled NumpyArrayWrapper. A non-object numpy dtype whose itemsize is 0 โ€” e.g. np.dtype('V0'), a zero-length void โ€” has hasobject == False, so read_array takes the raw-array else branch and evaluates min(BUFFER_SIZE, 0) == 0 as the divisor. This raises ZeroDivisionError before any array data is read.

The crash is count-independent: it fires for shape (0,) and (1,) alike, because the division happens before the read loop.

Asymmetry with the write side (confirms the missing check)

The WRITE path already guards the exact same division. In write_array (line ~126):

# joblib/numpy_pickle.py  (line ~126, write_array)
buffersize = max(16 * 1024 ** 2 // array.itemsize, 1)

The max(..., 1) floor prevents a zero divisor on write. The READ side has no equivalent floor โ€” an asymmetry that pinpoints the missing guard.

Reachability from the public loader

joblib.load() -> _unpickle -> NumpyUnpickler.load_build (overrides pickle BUILD) -> array_wrapper.read -> read_array. So a crafted .joblib file crashes the documented public loader. No pickle __reduce__ gadget is involved โ€” the crash comes purely from the array metadata joblib itself trusts and dereferences.


PoC

Craft a joblib file whose array placeholder claims a zero-itemsize dtype:

import numpy as np, pickle, joblib
from joblib.numpy_pickle import NumpyArrayWrapper

w = NumpyArrayWrapper(np.ndarray, shape=(1,), order='C',
                      dtype=np.dtype('V0'), allow_mmap=False)
open('poc_v0.joblib', 'wb').write(pickle.dumps(w, protocol=5))

joblib.load('poc_v0.joblib')   # -> ZeroDivisionError at numpy_pickle.py:191

The file is a plain pickle stream containing a NumpyArrayWrapper with dtype V0. joblib's NumpyUnpickler overrides BUILD, reconstructs the wrapper, and calls read_array, which divides by min(BUFFER_SIZE, itemsize) == 0.


Captured evidence (verbatim, real joblib.load() execution)

dtype V0 itemsize = 0 hasobject = False
Traceback (most recent call last):
  File ".../joblib/numpy_pickle.py", line 749, in load
    obj = _unpickle(fobj, ...)
  File ".../joblib/numpy_pickle.py", line 626, in _unpickle
    obj = unpickler.load()
  File "/usr/lib/python3.13/pickle.py", line 1256, in load
    dispatch[key[0]](self)
  File ".../joblib/numpy_pickle.py", line 462, in load_build
    _array_payload = array_wrapper.read(self, self.ensure_native_byte_order)
  File ".../joblib/numpy_pickle.py", line 284, in read
    array = self.read_array(unpickler, ensure_native_byte_order)
  File ".../joblib/numpy_pickle.py", line 191, in read_array
    max_read_count = BUFFER_SIZE // min(BUFFER_SIZE, self.dtype.itemsize)
                     ~~~~~~~~~~~~^^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ZeroDivisionError: integer division or modulo by zero

Negative controls (same run)

control1  legit np.arange dump/load roundtrip OK, equal = True
control2  identical craft path with VALID dtype 'u1' shape (0,) -> array([], dtype=uint8)  (loads fine)
variant   v0 shape (0,) -> ZeroDivisionError  (proves count-independent, pre-loop)

control2 isolates the cause: the same crafting method with a valid dtype loads cleanly, so the crash is specifically the itemsize-0 dtype metadata, not the way the file was built.


Suggested fix

Floor the divisor on the read side to mirror the write side, e.g.:

max_read_count = BUFFER_SIZE // max(min(BUFFER_SIZE, self.dtype.itemsize), 1)

or explicitly reject a zero/negative itemsize dtype before entering the read loop.


Dedup / prior art

  • Distinct from the three joblib scanner-bypass reports (stopbyte 0x2e-STOP, numeric-genops multi-array, numpy-ctypeslib). Those are modelscan pickle-layer false-negatives. This is a crash inside joblib's own read_array metadata handling, independent of any scanner, with no pickle reduce gadget.
  • Distinct from CVE-2024-34997 (joblib pickle __reduce__ RCE primitive) โ€” that requires a reduce gadget; this fires purely from trusted array metadata.
  • No public report found for a joblib read_array itemsize-0 ZeroDivisionError. The vulnerable line is unchanged on joblib main; the write/read guard asymmetry (write line ~126 uses max(..., 1)) is public but unremarked.
  • Related read_array paths characterized but weaker (not the primary): subarray dtype ('f8',(2,)) yields an unhandled ValueError (reshape mismatch); a huge shape yields numpy's guarded MemoryError (over-alloc is validated by numpy, not memory-unsafe). The itemsize-0 ZeroDivisionError is the clean, deterministic primary.

Scope note

joblib.load is already documented as unsafe for untrusted pickle input. The security boundary here is that a crash is reachable from pure array metadata before any reduce gadget executes โ€” i.e. metadata joblib itself parses and trusts triggers an unhandled exception. Impact is denial-of-service, not memory corruption or RCE.

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