Hotdry.

Article

FreeBSD setcred 内核提权漏洞:sizeof 类型错误引发的权限绕过链

分析 FreeBSD 14.x setcred 系统调用中的栈溢出漏洞,从 sizeof 类型错误到 SMAP/SMEP 绕过利用链的完整技术路径与缓解方案。

2026-05-21security

FreeBSD 14.x 系列近期曝出一个高危本地提权漏洞(CVE-2026-45250),该漏洞存在于 setcred(2) 系统调用的辅助组处理逻辑中。一个看似无害的 sizeof 类型错误,使得任何本地未授权用户都能在内核权限检查之前触发栈缓冲区溢出,最终实现从普通用户到 root 的完整权限提升。

漏洞根因:sizeof 的类型陷阱

漏洞位于 sys/kern/kern_prot.c 文件的 kern_setcred_copyin_supp_groups() 函数。该函数的 groups 参数被声明为 gid_t **const 类型,而代码中使用了 sizeof(*groups) 来计算缓冲区大小。在 LP64 架构上,指针大小为 8 字节,而 gid_t 实际只有 4 字节,这就导致了严重的计算偏差。

具体而言,当 sc_supp_groups_nb 为最大值 15 时,系统会分配一个 16 个元素的 gid_t 栈数组(共 64 字节),但实际执行 copyin 操作时却试图写入 15 * 8 = 120 字节的数据。这造成了 60 字节的栈溢出,且溢出数据完全来自用户空间可控的缓冲区。

更危险的是,这个溢出发生在 user_setcred() 调用 kern_setcred_copyin_supp_groups() 时(第 604 行),而真正的权限检查 priv_check_cred(PRIV_CRED_SETCRED) 直到第 623 行才执行。这意味着任何本地用户都能无限制地触发该漏洞。

利用链构建:从栈溢出到权限劫持

无 SMAP/SMEP 场景

在较旧内核或禁用缓解措施的系统上,60 字节的溢出足以覆盖 user_setcred() 栈帧中的多个被调用者保存寄存器。关键观察是 sys_setcred() 的函数序言只保存了 rbp/r14/rbx,却没有保存 r12。因此,被污染的 r12 会在函数返回时原样传播到 amd64_syscall()

amd64_syscall+0x155 处,内核执行以下指令序列:

mov  rcx, [r12 + 0x3f8]   ; r12 完全可控
mov  rdi, rbx              ; rdi = 真实 curthread
mov  esi, eax              ; esi = setcred 返回值
call [rcx + 0xc8]         ; 间接调用

这构成了一个两级间接调用劫持:攻击者通过控制 r12 指向用户空间伪造的结构,使内核执行任意代码。由于无 SMAP 保护,内核会正常解引用用户空间指针;无 SMEP 则允许直接跳转到用户空间 shellcode。

SMAP/SMEP 绕过:ZFS 模块 Gadget

现代内核启用 SMAP/SMEP 后,上述直接利用方式失效。研究人员发现 zfs.ko 模块中的 ZSTD_initCStream_advanced 函数包含一个关键 gadget:

lea  rdx, [rcx + 1]           ; rdx = K1 + 1
mov  qword ptr [rdi + 0x180], rax  ; td->td_ucred = K1+1

通过精心构造,攻击者可以让该 gadget 将当前线程的凭证指针 td_ucred 重定向到用户控制的内存区域。具体实现利用 setproctitle(2) 系统调用 —— 该调用允许未授权用户在 PARGS UMA 区域分配 256 字节的内核缓冲区,并将用户数据原样复制进去。

利用流程如下:父进程通过 setproctitle 在 PARGS slab 中布置伪造的 ucred 结构,关键字段包括 cr_uid/cr_ruid/cr_svuid 设为 0,cr_ref 设为高值以防止释放。子进程独立执行 setproctitleP_base - 1 写入自己的 PARGS 区域偏移 0xd0 处。当触发漏洞时,利用链读取子进程的该值作为 K1,使得 K1+1 恰好指向父进程的伪造凭证。

影响范围与修复状态

该漏洞影响 FreeBSD 14.3 和 14.4 的所有未补丁版本,在 SMAP/SMEP 启用的情况下仍可被完整利用。FreeBSD 15.0 虽然存在相同的源码级错误,但由于周围代码差异,漏洞只能导致内核 panic 而无法实现权限提升。FreeBSD 13.x 及更早版本不受影响,因为 setcred(2) 系统调用是在 14.x 中引入的。

FreeBSD 安全团队已于 2026-05-21 发布安全公告 FreeBSD-SA-26:18.setcred,并在 2026-05-20 向各分支推送补丁:

  • 14.3-RELEASE-p14
  • 14.4-RELEASE-p5
  • 15.0-RELEASE-p9

值得注意的是,该漏洞早在 2025-11-27 的 main 分支提交 000d5b52 中就被无意中修复 —— 该提交重构了相关函数,将 groups 参数从 gid_t ** 改为 gid_t *,并将 sizeof(*groups) 替换为 sizeof(gid_t)。安全团队随后将此修复回溯到所有受支持的分支。

缓解策略与检测建议

对于无法立即重启的系统,以下措施可降低风险:

1. 紧急补丁部署

freebsd-update fetch install
# 重启以加载新内核
shutdown -r now

2. 访问控制强化 限制本地 shell 访问权限,仅允许可信用户登录。该漏洞需要本地执行能力,无法通过网络直接触发。

3. 入侵检测指标 监控异常的 setcred(2) 调用,特别是带有 SETCREDF_SUPP_GROUPS 标志且 sc_supp_groups_nb 接近 15 的请求。同时关注 setproctitle 的异常使用模式。

4. 模块级缓解 虽然卸载 zfs.ko 可以移除 SMAP/SMEP 绕过路径中的关键 gadget,但这会影响 ZFS 文件系统的正常使用,仅作为临时应急措施。

技术启示

此漏洞再次验证了 C 语言中指针类型与 sizeof 操作的潜在风险。gid_t **gid_t * 的区别看似微小,却在 LP64 架构上导致了 2 倍的尺寸计算偏差。代码审查时应特别关注以下模式:

  • 双重指针解引用时的 sizeof 计算
  • 用户可控数据长度与内核缓冲区大小的边界检查
  • 特权操作与输入验证的时序关系

对于 FreeBSD 管理员而言,该漏洞的修复必须伴随系统重启才能生效,这是内核级补丁的固有特性。建议在维护窗口期内优先处理此更新。


参考来源

security

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com