Stack Buffer Overflow in mprintf.c formatting function (fallback path)
摘要
一个栈缓冲区溢出漏洞存在于 mprintf.c 文件的 out_double() 函数中。此漏洞影响编译时未定义 HAVE_SNPRINTF 的构建,这些构建被迫使用传统的 sprintf 函数。
负责计算浮点数格式化最大安全精度(maxprec)的逻辑无法正确处理负数。具体来说,它未能正确计算负数值整数部分所需的数字位数。结果导致缓冲区大小计算错误,使得后续的 sprintf 调用写入的数据量超过了固定大小的栈缓冲区容量。
受影响组件
- 文件:
lib/mprintf.c - 函数:
out_double() - 触发条件:编译时未定义
HAVE_SNPRINTF
技术细节
该函数使用了一个大小为 BUFFSIZE(326 字节)的本地栈缓冲区(work)。为了防止溢出,代码尝试估算浮点数值整数部分所需的数字位数,并据此减少允许的精度。
相关代码如下(约在master分支的第675行):
|
|
对于负数值(例如, -1.0e100),在进入循环时条件 val >= 10.0 为假,因此循环被完全跳过。结果就是 maxprec 没有被减少,即使格式化后的值的整数部分将占用大量的缓冲区空间。随后的 sprintf 调用会写入负号、完整的整数部分以及请求的小数精度,超出了326字节的栈缓冲区。
复现步骤
-
配置构建 编译
libcurl时未定义HAVE_SNPRINTF以强制使用后备的sprintf路径。模拟此情况的一种方法是修改lib/mprintf.c:1 2 3 4 5#if 0 /* 强制使用后备路径进行测试 */ (snprintf)(work, BUFFSIZE, formatbuf, dnum); #else (sprintf)(work, formatbuf, dnum); /* 易受攻击的路径 */ #endif -
编译复现程序 针对修改后的 libcurl 构建,编译以下 C 程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20#include <stdio.h> #include <curl/curl.h> #include <string.h> int main(void) { // -1.0e100 的整数部分需要约 102 个字符 // 精度 .300 需要 300 个字符用于小数部分 // 总输出长度约 402 个字符 // mprintf.c 中的栈缓冲区大小为 326 字节 double v = -1.0e100; char *output = curl_maprintf("%.300f", v); if (output) { printf("Output length: %lu bytes\n", (unsigned long)strlen(output)); curl_free(output); } return 0; } -
运行程序
-
观察 输出长度超过了 326 字节的缓冲区大小。根据栈保护机制(栈金丝雀,ASLR),这可能导致段错误或栈破坏检测。
建议的修复方案
通过操作绝对值(或等效逻辑)来修正整数位数的计算,以考虑负值:
|
|
可选地,限制 maxprec 防止下溢:
|
|
影响
此漏洞导致经典的栈缓冲区溢出。
- 可用性影响:拒绝服务(应用程序崩溃)
- 安全影响:在浮点数值和格式字符串受攻击者控制输入影响的情况下,此问题可能被利用来造成进一步的内存破坏,包括控制流操控。
- 影响范围:易受攻击的代码路径是传统后备实现的一部分,在现代默认的 curl 构建中不被使用。它主要影响传统的 Unix 系统、嵌入式平台或那些
snprintf不可用或明确禁用的自定义构建。
后续讨论与处理
bagder (curl staff) 评论:
@han_ank 请提及一个在使用未修改源代码的情况下会发生此问题的平台。
jimfuller2024 (curl staff) 评论:
哪些平台…可能是非常老的 Linux 发行版(我记得一些较旧的 BSD 变体,当然还有 System V!)…可能是一些嵌入式系统(TinyOS 或 FreeRTOS)…这里几乎没有“攻击面”,这可能表明我们可以完全弃用这一切。
jimfuller2024 (curl staff) 评论:
忘记提了——但我不确定 TinyOS 能否构建 curl…也许有人用 FreeRTOS 做过。
bagder (curl staff) 评论:
90年代有些系统没有
snprintf。我相信即使是那个时候非常老的 Windows 也可能没有。 在过去的二十年左右,即使是嵌入式系统也配备了提供snprintf的合适 libc 实现。我找不到过去几十年内不提供snprintf的系统。snprintf()是 C99 标准强制要求的,正如其名,该标准大约在 1999 年出台。旨在与 C99 兼容的 libc 实现则需要提供该函数。 由于我们找不到任何非遗留、已终止生命周期的系统,我们不能将此视为一个有效的漏洞——而是一个 Bug。 为了避免将来再次发生这种讨论,我们从代码中移除了sprintf()后备路径:https://github.com/curl/curl/pull/20218
ankitsingh015 评论:
感谢澄清。考虑到没有不提供
snprintf的受支持平台,我理解将其分类为 Bug 而非安全漏洞。我赞赏完全移除后备路径以避免未来问题的决定。
bagder (curl staff) 关闭了报告并将状态更改为 Informative:
感谢您的报告。现已修复此问题,因此关闭并标记为 Informative。
bagder (curl staff) 请求公开此报告:
根据项目透明政策,我们希望所有报告都公开披露。
ankitsingh015 同意公开此报告。 此报告已被公开。
报告信息
- 报告日期:2026年1月7日,UTC 22:12
- 报告人:ankitsingh015
- 报告对象:curl
- 报告ID:#3493602
- 状态:Informative
- 严重性:高 (7 ~ 8.9)
- 披露日期:2026年1月8日,UTC 09:36
- 弱点类型:经典缓冲区溢出
- CVE ID:无
- 赏金:无