FlawOpen

flawopen.com/Teardowns/cve-2023-6246-glibc-syslog-heap-buffer-overflow

● CVE-2023-6246 · CVSS 7.8 · 高危
安全研究 · FlawOpen

CVE-2023-6246:glibc syslog 堆缓冲区溢出

CVE-2023-6246 是 glibc syslog 的堆缓冲区溢出,由过长日志头触发。请更新到发行版提供的 glibc 修复包。

💡 通俗易懂的原理解析 (ELI5)

想象一名员工用短尺测量包裹和标签。标签太长,放不进尺子的测量范围,于是计算过程没有记录所需空间。员工随后订购了一个很小的盒子,却试图把完整标签和内容都印进去。在 glibc 中,过长的 syslog 头可能使所需大小没有被正确记录,导致日志函数只分配少量堆内存。

核心概念与专有名词

glibc
GNU C Library 提供大多数 Linux 程序使用的核心 C 运行时函数。
__vsyslog_internal()
glibc 内部实现,syslog() 和 vsyslog() 使用它来格式化消息及 SYSLOG_HEADER,然后交给系统日志服务。
SYSLOG_HEADER
syslog 消息前格式化的优先级和身份前缀,可包含程序名或通过 openlog() 提供的标识。
Heap-based buffer overflow (CWE-122)
写入操作超出堆上分配的缓冲区边界。此处的问题是在包含长名称的头部放不进初始固定缓冲区时,大小计算失败。

框架映射

  • 本指南所述弱点的 CWE 参考: CWE-122

根本原因剖析 (Root Cause)

glibc 公告指出,__vsyslog_internal() 格式化包含长程序名的 SYSLOG_HEADER 时,没有更新所需缓冲区大小。在漏洞代码中,头部长度 l 可能达到或超过 1,024 字节的栈缓冲区,因此跳过了格式化消息正文并设置 bufsize 的分支。之后的堆分配路径便使用未设置或为零的大小,再将头部和消息写入过小空间。Qualys 指出,长名称可以来自调用方控制的 argv[0] basename,或超过 1,024 字节的 openlog() 标识;若存在漏洞的 setuid 程序沿此路径记录日志,问题便可能影响安全。Qualys 在 Fedora 38 上通过 su 展示了本地提权。风险取决于本地访问、配置和调用路径;这不是通用的远程 syslog 攻击。

攻击执行流程分解

步骤 1

本地前提

本地进程必须通过使用受影响 glibc 的程序调用 syslog() 或 vsyslog()。要产生安全影响,还需要特权调用方,并且调用路径没有用安全且有界的标识替换程序名前缀。

步骤 2

超长日志头

日志头超过初始缓冲区|SYSLOG_HEADER 包含长程序名(__progname,来自 argv[0] 的 basename)或较长的 openlog() 标识。Qualys 表示触发值必须超过 1,024 字节。

步骤 3

大小计算遗漏

跳过大小计算分支|当日志头放不进 1,024 字节栈缓冲区时,漏洞代码会跳过计算 bufsize 的分支。后续堆分配空间不足以容纳头部和格式化消息。

步骤 4

内存影响

写入超出分配范围|日志函数将头部和消息写入过小缓冲区,从而破坏堆内存。Qualys 在 Fedora 38 上通过 su 展示本地提权;不要推断所有应用或发行版都具有相同可达性。

源代码对比:漏洞与安全实现

旧分支在头部无法放入栈缓冲区时让 bufsize 保持为零,因此后续堆分配可能过小。修复会将栈缓冲区标记为不可用,用它测量正文长度,记录 l + vl,再通过现有分配路径按所需大小分配。这些是上游补丁的重点片段,并非可独立使用的完整函数。

源码摘录: misc/syslog.c

存在漏洞的实现
/* misc/syslog.c 在提交 6bd0e4efcc78 之前的重点代码片段。 */
char bufs[1024];
char *buf = NULL;
size_t bufsize = 0;
...
if (0 <= l && l < sizeof bufs)
  {
    va_list apc;
    va_copy (apc, ap);
    int vl = __vsnprintf_internal (bufs + l, sizeof bufs - l, fmt, apc,
                                   mode_flags);
    if (0 <= vl && vl < sizeof bufs - l)
      buf = bufs;
    bufsize = l + vl;
    va_end (apc);
  }
/* 长请求头会跳过上方设置大小的分支。 */
加固后的安全修复
/* misc/syslog.c 在提交 6bd0e4efcc78 之后的重点代码片段。 */
char bufs[1024];
char *buf = bufs;
size_t bufsize;
...
if (0 <= l && l < sizeof bufs)
  {
    pos = bufs + l;
    len = sizeof (bufs) - l;
  }
else
  {
    buf = NULL;
    pos = bufs;
    len = sizeof (bufs);
  }
vl = __vsnprintf_internal (pos, len, fmt, apc, mode_flags);
if (!(0 <= vl && vl < len))
  buf = NULL;
bufsize = l + vl;

工程与系统安全加固清单

参考来源