漏洞概述
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 检查。
缓解策略与修复建议
临时缓解措施
在官方补丁可用前,建议采取以下措施降低风险:
- 限制本地访问:将受影响系统与不受信任的多用户环境隔离,仅允许受信任管理员进行本地登录
- 账户管控:审查并禁用不必要的本地账户,特别是具有 shell 访问权限的账户
- 监控审计:部署内核行为监控,检测异常的
setcred调用模式或特权升级行为 - 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 语言系统开发中具有普遍意义。
对于运维团队,建议建立以下防御体系:
- 版本管理:跟踪 FreeBSD 安全公告,及时应用内核补丁
- 纵深防御:即使存在内核漏洞,也应通过访问控制、最小权限原则限制攻击面
- 模块审计:审查内核模块加载策略,非必要模块应禁用
- 编译时检查:启用编译器的类型警告和静态分析工具,捕获潜在的 sizeof 误用
对于安全研究人员,该漏洞展示了现代内核缓解措施(SMAP/SMEP)的绕过技术,以及如何利用内核模块中的 gadget 完成利用链构造。这类技术对理解内核安全边界具有重要参考价值。
资料来源
- FatGid.io 技术报告: https://fatgid.io/
- FreeBSD-SA-26:18.setcred 安全公告
- CVE-2026-45250 (NVD)
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。