Hotdry.

Article

FreeBSD 14.x setcred 内核漏洞分析与缓解策略

深入剖析 FreeBSD 14.x setcred 系统调用中的 sizeof 类型错误导致的栈溢出漏洞,详解两种利用链技术路径及防御要点。

2026-05-21security

漏洞概述

CVE-2026-45250(代号 FatGid)是 FreeBSD 14.x 内核中的一个本地权限提升漏洞,源于 setcred(2) 系统调用实现中的类型错误。该漏洞允许任何未授权本地用户通过单次系统调用触发内核栈溢出,最终实现 root 权限获取。值得注意的是,漏洞触发点位于特权检查之前,这意味着攻击无需任何前置权限。

受影响版本包括 FreeBSD 14.4-RELEASE 和 stable/14 分支。FreeBSD 15.0 存在相同的源代码级错误,但由于周围代码结构差异,现有利用链无法直接移植。FreeBSD main 分支已于 2025-11-27 通过 commit 000d5b52c19ff3858a6f0cbb405d47713c4267a4 修复,但该修复尚未回退到稳定版分支。

sizeof 类型错误的根源

漏洞位于 sys/kern/kern_prot.c 中的 kern_setcred_copyin_supp_groups() 函数。该函数签名使用 gid_t **const groups 作为参数,而代码中两处使用 sizeof(*groups) 进行内存分配和数据拷贝:

/* 内存分配路径 */
*groups = wcred->sc_supp_groups_nb < CRED_SMALLGROUPS_NB ?
    smallgroups : malloc((wcred->sc_supp_groups_nb + 1) *
    sizeof(*groups), M_TEMP, M_WAITOK);

/* 数据拷贝路径 */
error = copyin(wcred->sc_supp_groups, *groups + 1,
    wcred->sc_supp_groups_nb * sizeof(*groups));

由于 groups 的类型是 gid_t **sizeof(*groups) 实际计算的是指针大小(LP64 架构下为 8 字节),而非预期的 gid_t 大小(4 字节)。当 sc_supp_groups_nb 取最大值 15 时,栈路径的拷贝操作写入 120 字节(15 × 8),但目标缓冲区 smallgroups[CRED_SMALLGROUPS_NB] 仅有 64 字节(16 × 4),导致 60 字节的栈溢出。

利用链技术解析

无 SMAP/SMEP 场景

在较旧内核或禁用缓解措施的环境中,60 字节的溢出覆盖了 user_setcred() 函数保存的寄存器槽位。关键观察是 sys_setcred() 的函数序言仅保存 rbp/r14/rbx未保存 r12。被篡改的 r12 值通过 user_setcred() 的尾声恢复后,向上传播至 amd64_syscall()

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

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

这是一个完全可控的两级间接调用:攻击者通过伪造 r12+0x3f8 处的指针获取 rcx,再通过 rcx+0xc8 确定最终调用目标。无 SMAP 时内核可解引用用户空间指针,无 SMEP 时可直接跳转到用户空间 shellcode。利用代码构造伪造的 struct sysentvec,使其 sv_set_syscall_retval 槽位指向用户 shellcode,后者通过 gs:[0] 获取真实线程指针,清零 td_ucred 中的 UID 字段完成提权。

SMAP/SMEP 安全场景

现代内核启用 SMAP(Supervisor Mode Access Prevention)和 SMEP(Supervisor Mode Execution Prevention)后,上述路径被阻断。研究人员发现 zfs.ko 模块中的 ZSTD_initCStream_advanced 函数包含一个可利用的 gadget:

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

rcx != 0 时,该 gadget 将 rcx + 1 写入 td->td_ucred,允许攻击者将线程凭证指针重定向到任意地址。利用策略是在父进程的 pargs slab 中布置伪造的 ucred 结构,在子进程的 pargs 中布置 K1 = P_base - 1,通过 fork 协作完成利用链。

伪造的 ucred 需要满足多个约束:高引用计数(0x7fffffff)防止 crfree 触发、有效的 cr_prison 指针、以及巧妙的 cr_groups 设置使其读取 prison0 结构的首四字节(pr_id = 0 即 wheel gid),从而通过内核的 groupmember 检查。

缓解策略与修复建议

临时缓解措施

在官方补丁可用前,建议采取以下措施降低风险:

  1. 限制本地访问:将受影响系统与不受信任的多用户环境隔离,仅允许受信任管理员进行本地登录
  2. 账户管控:审查并禁用不必要的本地账户,特别是具有 shell 访问权限的账户
  3. 监控审计:部署内核行为监控,检测异常的 setcred 调用模式或特权升级行为
  4. Jail 加固:审查 jail 配置,限制容器内用户的系统调用能力

根本修复方案

推荐方案:从 FreeBSD main 分支 cherry-pick commit 000d5b52c19ff3858a6f0cbb405d47713c4267a4 到本地内核源码树,重新编译并安装内核。该提交将 groups 参数从 gid_t ** 改为局部 gid_t *,并将两处 sizeof(*groups) 替换为明确的 sizeof(gid_t)

官方补丁:FreeBSD 安全团队已发布 FreeBSD-SA-26:18.setcred 安全公告,建议关注官方更新渠道。

无法生效的缓解手段

以下措施对该漏洞无效:

  • 移除 setuid 二进制文件:利用链完全在内核空间执行
  • 禁用 kldload:SMAP/SMEP 安全利用仅依赖已加载的 zfs.ko
  • 限制 setcred(2) 系统调用:会破坏 FreeBSD 原生 API 的兼容性

防御要点总结

FatGid 漏洞揭示了类型安全在系统编程中的重要性。单个 sizeof 表达式的误用导致严重的安全后果,这一教训在 C 语言系统开发中具有普遍意义。

对于运维团队,建议建立以下防御体系:

  1. 版本管理:跟踪 FreeBSD 安全公告,及时应用内核补丁
  2. 纵深防御:即使存在内核漏洞,也应通过访问控制、最小权限原则限制攻击面
  3. 模块审计:审查内核模块加载策略,非必要模块应禁用
  4. 编译时检查:启用编译器的类型警告和静态分析工具,捕获潜在的 sizeof 误用

对于安全研究人员,该漏洞展示了现代内核缓解措施(SMAP/SMEP)的绕过技术,以及如何利用内核模块中的 gadget 完成利用链构造。这类技术对理解内核安全边界具有重要参考价值。


资料来源

  • FatGid.io 技术报告: https://fatgid.io/
  • FreeBSD-SA-26:18.setcred 安全公告
  • CVE-2026-45250 (NVD)

security

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

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