[控场AI]
· 6 分钟阅读· 3,011 字

GGUF解析器溢出漏洞的静态检测方案

GGUF解析器溢出漏洞的静态检测方案

GGUF解析器反复因文件头整数溢出导致越界,可在加载前静态拦截。

GGUF作为主流推理框架的模型容器格式本身没有代码执行面,但其手写的C/C++解析器反复因文件头中的计数值、长度、维度或偏移量触发整数溢出,最终演变为堆越界读写。已公开的漏洞集群涵盖llama.cpp与PowerInfer的7个CVE,触发值全部位于文件头,因此可在加载前静态检测。作者MV2发布了gguf-scan工具、CVE映射表与畸形头部测试语料生成器,形成一套低成本、可复现的头部校验方案。工具通过GG003、GG004、GG009、GG011等规则与漏洞对应,但存在明确边界:PowerInfer数据偏移仅部分覆盖,且扫描通过不代表文件安全,运行时补丁仍需持续跟进。

GGUF解析器溢出漏洞的静态检测方案

GGUF作为llama.cpp、Ollama、LM Studio等主流推理框架的模型容器格式,本身并无代码执行面——它不含pickle序列化,也没有嵌入式脚本,打开文件并不会"运行"任何东西。但问题出在解析器层:手写的C/C++ ggml读取代码及其衍生实现,一次次因畸形文件头中的计数值、长度值、维度值或偏移量触发整数溢出,最终演变为堆越界读写。

GGUF解析器溢出漏洞的静态检测方案

一个已经公开的漏洞集群

这些漏洞有一个共同特征:触发值全部位于文件头中。这意味着一个加载前的静态检查,完全可以在解析器接触文件之前就读取文件头并标记出危险信号。

据开发者MV2(Munim, Inc.)整理的公开数据显示,目前已披露的相关漏洞构成一个可辨识的集群。按CWE分类,这些漏洞几乎全部集中在整数溢出引发的越界访问上:

  • CVE-2024-21825:llama.cpp GGUF数组/字符串解析中的元数据数组长度溢出;
  • CVE-2024-23496:llama.cpp gguf_fread_str中的元数据字符串长度溢出;
  • CVE-2024-23605:llama.cpp n_kv处理中的元数据KV计数溢出;
  • CVE-2025-53630:llama.cpp gguf_init_from_file_impl中的张量计数(尺寸累加)溢出;
  • CVE-2026-27940:llama.cpp mem_size中绕过上述53630修复的张量计数溢出;
  • CVE-2026-33298:llama.cpp ggml_nbytes()中的张量维度溢出;
  • CVE-2026-108955:PowerInfer gguf_init_from_file中的张量数据偏移溢出。

这些漏洞的共同点值得注意:它们不是随机分布的,而是反复出现在同一类代码路径上——文件头字段的解析与累加计算。换句话说,整个攻击面可以被归结为"文件头中的整数"。

GGUF文件头结构简述:GGUF文件以固定魔数(0x46554747,即"GGUF")开头,随后依次是版本号(uint32)、张量数量(uint64)、元数据KV对数量(uint64),再跟一段连续的KV元数据区,然后是张量信息区(每项包含名称、维度数、各维长度、类型、数据偏移),最后才是张量数据区。整个攻击面集中在这些头部字段上:n_kv、n_tensors、字符串长度、维度数组长度、各维尺寸以及张量偏移量,全部是整数。当解析器对这些值做累加、乘法或用作分配/拷贝长度时,若缺少溢出检查,就会算出错误的小值或负值,导致后续按错误尺寸访问缓冲区。理解这个布局,就能理解为什么"只读头部"的扫描器可以覆盖这整类漏洞。

静态扫描工具的设计思路

针对上述模式,MV2发布了名为gguf_scan.py(命令行命令为gguf-scan)的头部校验工具,采用Apache-2.0许可,仅依赖标准库。它的核心设计原则是:只解析GGUF文件头,绝不加载或执行模型。

配套发布的还有两份材料。gguf_cve_map.json与GGUF_CVE_MAP.md(CC BY 4.0许可)将每一条公告映射到具体溢出的头部字段、对应的CWE编号,以及能检测该漏洞的扫描规则编号。据作者称,这是该集群首个整合且可复现的映射表。gguf_cve_corpus.py(Apache-2.0)则负责生成仅包含畸形文件头的测试文件。

在CI中集成的成本极低,只需一行:

gguf-scan ./my-model.gguf --fail-on HIGH

如果要对自己的GGUF解析器做回归测试:

python gguf_cve_corpus.py --out-dir DIR

该命令会为映射表中的每一行生成一个极小的、仅含文件头的.gguf文件,外加一个零对齐的纵深防御用例。任何实现了正确边界检查的读取器都应拒绝这些文件。

整数溢出如何演变为堆越界:以CWE-190(整数溢出/回绕)为例,解析器常用uint64_t total = n * size来累加张量所需内存。若n与size都来自文件头且乘积超过UINT64_MAX,结果会回绕成一个很小的数;解析器据此分配一小块堆内存,随后按n个元素写入,即发生堆缓冲区溢出(CWE-787)或越界读(CWE-125)。另一种常见情形是有符号整数溢出:int型长度字段被赋一个接近INT_MAX的值,加上偏移后变为负数,绕过长度校验后被作为size_t传入memcpy,同样越界。CWE-787与CWE-125正是这类漏洞的常见归类,其根因则是缺少前置的溢出检查。

映射规则与实际覆盖情况

工具通过若干规则(GG003、GG004、GG006、GG009、GG011等)与漏洞一一对应。例如GG003对应张量计数/尺寸累加类溢出,GG004对应元数据字符串长度溢出,GG009对应张量维度溢出。

你可能没注意到,PowerInfer那个案例只被部分覆盖:GG011仅能标记未对齐的数据偏移,而对数据区域的完整边界检查仍是一个已知缺口。这一点作者在文档中如实标注,没有掩饰。

部分覆盖的含义:所谓"部分覆盖",指扫描器只能检测某一类可静态判定的条件,而无法证明对应代码路径在运行时是安全的。以PowerInfer的gguf_init_from_file为例,GG011可以标记出数据偏移未按张量类型所需对齐方式对齐的情况,从而提示潜在风险;但真正是否越界,还取决于后续mmap/fread的长度计算与实际文件长度是否匹配。如果偏移恰好落在合法范围内却指向错误的数据区,或与其他字段组合后才越界,静态头部扫描无法单独判定。这就解释了为什么作者将某些CVE标为部分覆盖,而未夸大工具的检测能力。

工具的诚实边界

作者对工具有着清晰的自我限定,这几点对使用者来说同样重要:

  • 所有列出的漏洞均为已公开信息,不涉及任何零日漏洞,也没有披露任何新的漏洞细节;
  • 构造的测试文件仅有文件头,不含张量数据,也不含利用载荷,只是复现了畸形的头部取值;
  • 扫描通过不代表文件安全。它仅意味着文件头通过了这些特定检查,并不能证明文件本身或其权重是安全的,也不能证明你的加载器没有其他缺陷。运行时补丁仍需持续跟进;
  • 覆盖范围仅限于文件头层级,且PowerInfer的数据偏移案例只是部分覆盖。

这套工具的价值不在于"消灭"所有GGUF安全风险,而在于把一类反复出现的、可被精确建模的漏洞,在企业或个人的模型加载流水线中前置拦截。在模型文件来源日益多样化的当下,这种"加载前拦截"的思路显然有其现实意义。

代码仓库与映射表可在GitHub上的munimtechnologies/keras-model-scan项目获取。

#GGUF#整数溢出漏洞#llama.cpp安全#gguf-scan#模型文件解析#CVE漏洞检测#静态分析工具
分享:

相关推荐