# Uncaught tokenize.TokenError in np.load() via crafted NPY header **Target:** numpy/numpy **Platform:** huntr.com **Severity:** High **CWE:** CWE-248 (Uncaught Exception) **Affected versions:** numpy 2.3.5 (confirmed), main branch commit b6e8ad8 (confirmed) **Python versions affected:** 3.12+ (where C tokenizer enforces nesting limit) --- ## Summary Loading a crafted NPY file causes numpy.load() to raise tokenize.TokenError instead of the documented ValueError. The exception is not a subclass of ValueError and is not caught anywhere in the numpy loading path. Any application that handles numpy loading errors by catching ValueError will have the TokenError crash it unexpectedly. --- ## Vulnerability Details **Type:** Uncaught Exception / Improper Exception Handling **CWE:** CWE-248 **Affected file:** numpy/lib/_format_impl.py **Affected function:** _read_array_header() (line 659-666) and _filter_header() (line 586-618) **Affected NPY versions:** v1.0 and v2.0 format files (not v3.0) --- ## Root Cause In _read_array_header(), when ast.literal_eval() fails with SyntaxError parsing the header, a Python 2 backwards-compatibility fallback calls _filter_header(): ```python # numpy/lib/_format_impl.py, line 656-666 try: d = ast.literal_eval(header) except SyntaxError as e: if version <= (2, 0): header = _filter_header(header) # <-- raises TokenError try: d = ast.literal_eval(header) except SyntaxError as e2: raise ValueError(...) from e2 ``` _filter_header() calls tokenize.generate_tokens() (line 608). Python 3.12+ enforces a nesting depth limit in its C tokenizer. When a header has 100+ nested parentheses, tokenize.generate_tokens() raises tokenize.TokenError with the message "too many nested parentheses". TokenError is NOT a subclass of ValueError. It is not caught by the except SyntaxError handler above, nor by any other handler in the numpy call stack. It propagates all the way out of np.load() as an undocumented exception. **The full exception hierarchy:** ``` tokenize.TokenError -> Exception -> BaseException NOT related to: ValueError (what np.load() documentation says to catch) OSError ``` --- ## Trigger Construction A crafted NPY v1.0 file with a dtype descriptor having 100+ nesting levels. The header must: 1. Have a descr field with >= 100 nested list-tuple structures 2. Be <= 10,000 characters (the max_header_size limit) Example descriptor at depth=100 (fits in 1,147 chars, well under 10,000 limit): ``` [('f99', [('f98', [('f97', ... [('f0', '