FatGid:FreeBSD 14.x 内核本地权限提升

2026-05-21 1 阅读 WhyNotHugo
漏洞详细信息 文件:sys/kern/kern_prot.c 函数:kern_setcred_copyin_supp_groups() 行:528-533 函数签名使用双指针作为 groups 参数: static int kern_setcred_copyin_supp_groups( struct setcred * const wcred, const u_int flags, gid_t * const smallgroups, gid_t ** const groups) 因为 groups 的类型为 gid_t ** ,所以表达式 sizeof(*groups) 在 LP64 上计算结果为 sizeof(gid_t *) == 8 ,而不是预期的 sizeof(gid_t) == 4 。此 sizeof 表达式用在两个地方: /* 第 528-530 行:分配 */ *groups = wcred->sc_supp_groups_nb < CRED_SMALLGROUPS_NB ?小组:malloc((wcred->sc_supp_groups_nb + 1) * sizeof(*groups) , M_TEMP, M_WAITOK); /* sizeof(*groups) == 8 */ /* 第 532-533 行:copyin */ error = copyin(wcred->sc_supp_groups, *groups + 1, wcred->sc_supp_groups_nb * sizeof(*groups) ); /* sizeof(*groups) == 8 */ 堆路径上的分配是 2× oversized,这是安全的。但是,对于堆栈路径(当 sc_supp_groups_nb < CRED_SMALLGROUPS_NB == 16 时), *groups 设置为smallgroups,即在调用者 user_setcred() 中声明为局部变量的 gid_t[CRED_SMALLGROUPS_NB] 数组: gid_tsmallgroups[CRED_SMALLGROUPS_NB]; /* 16 * 4 = 64 字节 */ 复制目标是 *groups + 1 == &smallgroups[1] ,这留下了 15 * 4 == 60 字节的可用空间。 copyin 复制 sc_supp_groups_nb * sizeof(*groups) == sc_supp_groups_nb * 8 字节。当最大堆栈路径值 sc_supp_groups_nb == 15 时:写入的字节数:15 * 8 = 120 缓冲区容量:15 * 4 = 60 溢出:smallgroups[] 末尾后面的 60 个字节 溢出是用用户空间中完全由攻击者控制的数据写入的( wcred->sc_supp_groups 指向攻击者提供的缓冲区)。触发路径和权限检查顺序 溢出发生在 kern_setcred_copyin_supp_groups() 中,它是在权限检查之前的第 604 行从 user_setcred() 调用的。特权检查 ( priv_check_cred(PRIV_CRED_SETCRED) ) 不会发生,直到在第 623 行调用 kern_setcred() 以及在该函数的第 813 行调用。任何本地用户都可以通过发出以下命令来触发溢出: setcred(SETCREDF_SUPP_GROUPS, &wcred, sizeof(wcred)) ,其中 wcred.sc_supp_groups_nb == 15 和wcred.sc_supp_groups 指向 15 * 8 == 120 字节的用户空间缓冲区。 LPE 技术(无 SMAP、无 SMEP) 60 字节溢出会破坏 user_setcred() 序言中每个被调用者保存的寄存器槽(保存的 RBP 除外)。 14.4 GENERIC 上的编译器排序将损坏窗口置于 [rbp - 0x40 .. -0x05] 处: buf[60..67] mac.m_buflen buf[68..75] mac.m_string buf[76..83] td 指针溢出 <- 控制 kern_setcred(td=...) buf[84..91] 保存的 rbx buf[92..99] saving r12 <- 向上传播堆栈 buf[100..107] saving r13 buf[108..115] saving r14 buf[116..119] 保存 r15 的低 32 位 关键的观察结果是 sys_setcred() 的序言仅保存 rbp/r14/rbx - 它不保存 r12 。因此,由 user_setcred() 的尾声弹出的损坏的 r12 会通过 sys_setcred() 传播到 amd64_syscall() ,它在 +0x155 处将其用作实时 td_proc 指针: ffffffff8105b6e5: mov rcx, [r12 + 0x3f8] ; r12 完全控制 ffffffff8105b6ed: mov rdi, rbx ; rdi = 真实线程 ffffffff8105b6f0: mov esi, eax ; esi = setcred retval ffffffff8105b6f2:调用 [rcx + 0xc8] ;间接调用 这是完全由攻击者控制的两级间接调用: *(r12+0x3f8) 提供 rcx ,而 *(rcx+0xc8) 是调用目标。如果没有 SMAP,内核会愉快地取消引用用户模式指针,因此这两种间接寻址都可以通过放置在用户内存中的虚假结构来满足。如果没有 SMEP,间接调用可能会针对用户空间代码。已发布的 no-SMAP 漏洞利用构造了一个伪造的 struct sysentvec,其 sv_set_syscall_retval 槽(偏移量 0xc8 )指向用户空间 shellcode。 shellcode 读取真实 curthread 的 gs:[0] ,恢复 r12 ,然后将真实 td_ucred 上的 cr_uid/cr_ruid/cr_svuid/cr_rgid/cr_svgid 归零并返回。 LPE 技术(SMAP/SMEP,无信息泄漏) amd64_syscall+0x155 处的链原语通过 rcx = K1(攻击者选择的 8 字节值)达到其目标。如果目标小工具将 rcx + 1 写入 td->td_ucred ,则当前线程的凭证指针现在设置为我们选择的任何地址 - 如果该地址恰好位于我们控制的内核缓冲区(堆驻留的 pargs 板)内,我们在那里植入的伪造凭证将立即生效。该小工具位于 zfs.ko 内,位于 ZSTD_initCStream_advanced 中:push rbp; mov rbp, rsp 推 r15;推r14;推 rbx 子 rsp,0x38 mov rbx,rdx mov r14,rsi mov r15,rdi ; r15 = arg1 = real_td