在图像处理库中,libpng 作为 PNG 格式的标准实现,被广泛用于浏览器、图像查看器和游戏引擎等软件。buffer overflow 是其经典安全隐患,尤其在解析复杂 PNG chunk 时,边界检查缺失易导致堆溢出或越界读取。2025 年 11 月 22 日发布的 libpng 1.6.51 版本,通过 fuzzing 和安全研究修复了四个此类漏洞(CVE-2025-64505、CVE-2025-64506、CVE-2025-64720、CVE-2025-65018),其中两个高危(CVSS 7.1),两个中危(CVSS 6.1)。这些漏洞影响 1.6.0 至 1.6.50 版本,需要用户处理恶意 PNG 文件才能触发,但可能造成信息泄露、拒绝服务甚至任意代码执行。
漏洞成因剖析:PNG 解析中的边界失控
PNG 文件结构复杂,包括 IHDR(图像头)、PLTE(调色板)、IDAT(图像数据)等 chunk。libpng 在处理这些 chunk 时,依赖动态分配的堆缓冲区进行解压、转换和合成。漏洞根源在于未严格验证输入尺寸与输出缓冲匹配,尤其在量化(quantize)、调色板预乘(premultiplication)和行组合(row combine)阶段。
-
CVE-2025-64505(中危,堆缓冲区 over-read):发生在 png_do_quantize 函数中,恶意调色板索引超出 palette 数组边界。fuzz 输入构造畸形 palette chunk,导致读取未初始化内存。证据:Samsung-PENTEST 报告显示,索引值 > 255 时,直接偏移 palette[invalid],泄露堆内容。
-
CVE-2025-64506(中危,堆缓冲区 over-read):png_write_image_8bit 函数在 8-bit 输入且启用 convert_to_8bit 时,边界未对齐。weijinjinnihao fuzz 发现,输入尺寸计算忽略了通道转换,导致 over-read。
-
CVE-2025-64720(高危,out-of-bounds read):png_image_read_composite 中的调色板预乘操作,结合 PNG_FLAG_OPTIMIZE_ALPHA 标志。Samsung-PENTEST 指出,premultiplication 时 alpha 通道未检查像素索引,读取超出合成缓冲区。
-
CVE-2025-65018(高危,堆缓冲区 overflow):最典型一例,焦点在简化 API png_image_finish_read 处理 16-bit 交错(Adam7)PNG 时请求 8-bit 输出。用户按 PNG_IMAGE_SIZE(image) 分配缓冲(假设 8-bit RGBA,32x32 像素仅 4096 字节),但 png_combine_row 使用 IHDR 声明的 16-bit 深度(6 字节/像素)写入,导致 6144 字节溢出 2048 字节。更大图像(如 256x256)溢出达 131072 字节。“libpng 1.6.51 修复了 CVE-2025-65018,该漏洞在处理 16-bit 交错 PNG 时导致堆溢出。”[1] yosiimich 通过 fuzz 报告,Fabio Gritti 和 John Bowler 分析确认,仅交错图像受影响,非交错路径安全。
这些漏洞凸显 fuzzing(如 AFL 或 libFuzzer)在发现边界问题的价值:随机变异 PNG chunk(如 bit-depth=16, interlace=Adam7),结合 AddressSanitizer(ASan)快速定位。
补丁实现:精确边界检查与缓冲策略
libpng 维护者 Cosmin Truta 在 GitHub commits 中实现了针对性修复,避免破坏 API 兼容性。
-
通用策略:所有修复前置边界检查,如索引 < palette_size,row_bytes <= allocated_size。使用 png_error 提前返回,防止下游崩溃。
-
CVE-2025-65018 深度补丁:
初始 commit 16b5e38 添加 bit-depth 验证:若 IHDR=16-bit 且输出非 linear(8-bit),拒绝处理。但这过于严格,破坏合法非交错场景。
最终 commit 218612d 重构:引入 do_local_scale 标志,仅交错图像启用中间缓冲 local_row(png_malloc(png_get_rowbytes(png_ptr, info_ptr)) 大小为转换后 8-bit)。流程:
- png_set_scale_16 启用 16-to-8 缩放。
- 若 interlaced,分配 local_row。
- png_image_read_direct_scaled:逐 pass png_read_row 到 local_row(已转换 8-bit),memcpy 到用户缓冲。
- png_free(local_row) 释放。
这确保 png_combine_row 只写到足够缓冲,避免 IHDR 深度溢出,同时保持非交错快速路径。
其他 CVE 类似:png_do_quantize 添加 index < num_palette;png_write_image_8bit 调整 convert_to_8bit 边界。
可落地工程参数与清单
升级 libpng 是首要,但工程化需参数化防御。
-
升级清单:
- 立即拉取 v1.6.51:
git checkout v1.6.51 或下载 release。
- 应用所有修复 commit:6a528eb(64505)、2bd84c0(64506)、08da33b(64720)、16b5e38+218612d(65018)。
- 验证:编译测试套件,ASan 无 overflow。
-
fuzzing 参数配置(用 libFuzzer):
./fuzz_png_read $CORPUS_DIR -max_len=10M -timeout=10 -runs=1e8
- corpus: 合法 PNG +变异 chunk(bit-depth=16, interlace=1, malformed palette)。
- 监控:ASan stack-use-after-scope, heap-buffer-overflow。
- 阈值:超时 10s,长度上限 10MB(PNG 典型)。
-
运行时防御参数:
| 参数 |
值 |
作用 |
| PNG_SAFE_READ |
1 |
启用安全读取,拒绝畸形 chunk |
| PNG_USER_MEM |
custom_alloc |
带 canary 的堆分配 |
| ASan options |
halt_on_error=1 |
立即崩溃报告 |
| Valgrind |
--tool=memcheck --track-origins=yes |
检测 uninit read |
-
监控与回滚:
- Prometheus 指标:png_read_errors_total >5/分钟 告警。
- 沙箱隔离:seccomp 限制 mprotect,防止 RCE。
- 回滚阈值:崩溃率 >1%,降级到 1.6.50 + 手动 patch。
-
代码清单:安全 PNG 加载:
png_image image;
memset(&image, 0, sizeof(image));
image.version = PNG_IMAGE_VERSION;
if (png_image_begin_read_from_file(&image, filename)) {
image.format = PNG_FORMAT_RGBA;
size_t size = PNG_IMAGE_SIZE(image);
void* buffer = malloc(size);
if (buffer && png_image_finish_read(&image, NULL, buffer, 0, NULL)) {
}
free(buffer);
}
添加:检查 image.warning_or_error=0。
这些实践确保内存安全,即使上游有遗漏。libpng 事件提醒:图像库需 fuzz 覆盖率 >90%,边界参数化。
资料来源:
[1] https://www.openwall.com/lists/oss-security/2025/11/22/1
[2] https://github.com/pnggroup/libpng/security/advisories/GHSA-7wv6-48j4-hj3g
[3] https://github.com/pnggroup/libpng/commit/16b5e3823918840aae65c0a6da57c78a5a496a4d 等 commits。
(正文字数:1256)