前言:
眼前朋友们对“ubuntu报错缓冲区溢出”大体比较注意,各位老铁们都需要分析一些“ubuntu报错缓冲区溢出”的相关知识。那么小编在网上汇集了一些对于“ubuntu报错缓冲区溢出””的相关知识,希望我们能喜欢,小伙伴们快快来了解一下吧!更多互联网精彩资讯、工作效率提升关注【飞鱼在浪屿】(日更新)
摘要
=========================== =========================================
一个Linux内核文件系统层的size_t- to-int转换漏洞:通过创建、挂载、删除
总路径长度超过1GB的深层目录结构,非特权本地攻击者可以将10字节的字符串“//deleted”写入 vmalloc() 内核缓冲区的开头下方,正好偏移量 -2GB-10B的位置。
利用了这种不受控制的越界写入,并得了默认安装的 Ubuntu 20.04 的完全 root 权限,
Ubuntu 20.10、Ubuntu 21.04、Debian 11 和 Fedora 34 工作站;其他Linux 发行版当然容易受到攻击,并且可能会被利用。
我们的漏洞利用需要大约 5GB 的内存和 1M 的 inode;
可从以下网址获得:https : // 详细介绍
分析
===================================================================
Linux 内核的 seq_file 接口产生包含记录序列的虚拟文件(例如,/proc 中的许多文件是
seq_files,而记录通常是行)。每个记录都必须适合一个seq_file 缓冲区,因此可以根据需要扩大它,方法是
在第 242 行将其大小加倍(seq_buf_alloc() 是 kvmalloc() 的简单包装器):
------------------------------------------------------------------------ 168 ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter) 169 { 170 struct seq_file *m = iocb->ki_filp->private_data; ... 205 /* grab buffer if we didn't have one */ 206 if (!m->buf) { 207 m->buf = seq_buf_alloc(m->size = PAGE_SIZE); ... 210 } ... 220 // get a non-empty record in the buffer ... 223 while (1) { ... 227 err = m->op->show(m, p); ... 236 if (!seq_has_overflowed(m)) // got it 237 goto Fill; 238 // need a bigger buffer ... 240 kvfree(m->buf); ... 242 m->buf = seq_buf_alloc(m->size <<= 1); ... 246 }------------------------------------------------------------------------
这个大小乘法本身并不是一个漏洞,因为m->size 是一个 size_t(一个无符号的 64 位整数,在 x86_64 上),并且系统会在这个乘法溢出整数 m->size之前就耗尽内存。
不幸的是,这个 size_t 也被传递给 一个度量用 int类型作为参数的函数(有符号的 32 位整数)而不是 size_t 的函数。例如,show_mountinfo() 函数(在第 227 行调用以格式化
/proc/self/mountinfo 中的记录)调用 seq_dentry()(在第 150 行),它
调用 dentry_path()(在第 530 行),它调用prepend()(在第 387 行):
------------------------------------------------------------------------135 static int show_mountinfo(struct seq_file *m, struct vfsmount *mnt)136 {...150 seq_dentry(m, mnt->mnt_root, " \t\n\\");------------------------------------------------------------------------ 523 int seq_dentry(struct seq_file *m, struct dentry *dentry, const char *esc) 524 { 525 char *buf; 526 size_t size = seq_get_buf(m, &buf); ... 529 if (size) { 530 char *p = dentry_path(dentry, buf, size);------------------------------------------------------------------------380 char *dentry_path(struct dentry *dentry, char *buf, int buflen)381 {382 char *p = NULL;...385 if (d_unlinked(dentry)) {386 p = buf + buflen;387 if (prepend(&p, &buflen, "//deleted", 10) != 0)------------------------------------------------------------------------ 11 static int prepend(char **buffer, int *buflen, const char *str, int namelen) 12 { 13 *buflen -= namelen; 14 if (*buflen < 0) 15 return -ENAMETOOLONG; 16 *buffer -= namelen; 17 memcpy(*buffer, str, namelen);------------------------------------------------------------------------
结果,如果一个无特权的本地攻击者创建、挂载和删除总路径长度超过 1GB 的深层目录结构,如果攻击者 调用open() 和 read()函数操作/proc/self/mountinfo,则:
- 在 seq_read_iter() 中,使用vmalloc() 分配了2GB 缓冲区(第242行),并
调用 show_mountinfo()(第 227 行);
- 在 show_mountinfo() 中,使用空的 2GB 缓冲区调用 seq_dentry()(第 150 行);
- 在 seq_dentry() 中,调用dentry_path() 携带参数2GB 大小(第 530 行);
- 在 dentry_path() 中,因此 int buflen 为负数 (INT_MIN,-2GB),p 指向 vmalloc()分配的 缓冲区下方 -2GB 的负偏移量(第 386 行),并调用 prepend()(第 387 行);
- 在 prepend() 中,*buflen 减去10 个字节并变成一个大但是是正的整数(第 13 行),*buffer 减少 10 字节,指向vmalloc()ated 缓冲区下方 -2GB-10B 的偏移量(第 16 行),
写入 10 字节大小的字符串“//deleted”(第 17 行)。
漏洞利用概述
===================================================================
1/ mkdir () 一个总路径长度超过 1GB的深层目录结构(大约 1M 个嵌套目录),我们将其绑定挂载到非特权用户命名空间中,然后 rmdir() 将其挂载。
2/ 创建一个线程,它 vmalloc() 访问一个小的 eBPF 程序(通过BPF_PROG_LOAD),然后我们阻止这个线程(通过 userfaultfd 或 FUSE),在内核进行 JIT 编译之前,eBPF 程序已经通过内核 eBPF 验证程序的验证
3/ 我们在我们的非特权用户命名空间中 open() /proc/self/mountinfo ,并开始 read() 我们绑定挂载目录的长路径,从而将字符串“//deleted”写入正好 -2GB 的偏移量-10B 低于
vmalloc() 化缓冲区的开头。
4/ 安排这个“//deleted”字符串覆盖经过验证的eBPF程序的一条指令(从而使
内核eBPF验证器的安全检查无效),并将这个不受控制的越界写入转化为信息暴露,并进入有限但受控的越界写入。
5/通过重用 Manfred Paul 漂亮的 btf 和map_push_elem 技术,将这个有限的越界写入转换为内核内存的任意读写:https :// /cve-2020-8835-linux-kernel-privilege-escalation-via-improper-ebpf-program-verification
6/ 使用这个任意读取来定位内核内存中的 modprobe_path[] 缓冲区,并使用任意写入来替换
这个缓冲区的内容(默认为“/sbin/modprobe”)和我们自己的可执行文件的路径,从而获得完整的 root 权限。
漏洞利用细节
====================================================================
a/ 创建了一个总路径长度超过1GB的目录:理论上,需要创建超过1GB/256B=4M的嵌套目录(NAME_MAX 为 255);实际上,show_mountinfo() 将我们长目录中的每个 '\\' 字符替换为 4 字节字符串“\\134”,因此我们只需要创建 1M 嵌套目录。
b/ 我们填补了所有大的 vmalloc 漏洞:
在几个非特权用户命名空间中绑定挂载 (MS_BIND)长目录的各个部分,并
通过 read()ing /proc/self/mountinfo 来 vmalloc() 分配 大 seq_file 缓冲区。
例如,我们在漏洞利用中 vmalloc() 占用了 768MB 的大缓冲区。
c/ vmalloc() 使用了两个 1GB 缓冲区和一个 2GB 缓冲区(通过在三个不同的用户命名空间中绑定挂载我们的长目录,并通过 read()ing
/proc/self/mountinfo),检查“//deleted " 确实写入在 2GB 缓冲区开头下方的 -2GB-10B 偏移量(即,第一个 1GB 缓冲区开头上方的 8182B——“XXX”是保护页):
"//deleted" | 4KB v 1GB 4KB 1GB 4KB 2GB-----|---|---+-------------|---|-----------------|---|-----------------| ... |XXX| seq_file buffer |XXX| seq_file buffer |XXX| seq_file buffer |-----|---|---+-------------|---|-----------------|---|-----------------| | | | | \----<----<----<----<----<----<----<----/ 8182B -2GB-10B
d/ 填写所有小的 vmalloc 漏洞:vmalloc()通过发送 () 大量 NETLINK_USERSOCK 消息来消耗各种小套接字缓冲区。例如,在漏洞利用中 vmalloc() 占用了 256MB 的小缓冲区。
e/ 创建了 1024 个用户空间线程;每个线程开始将 eBPF程序加载到内核中,但是(通过 userfaultfd 或 FUSE)阻塞了内核空间中的每个线程(在第 2101 行),在 eBPF 程序
实际上被 vmalloc() 化之前(在第 2162 行):
------------------------------------------------------------------------2076 static int bpf_prog_load(union bpf_attr *attr, union bpf_attr __user *uattr)2077 {....2100 /* copy eBPF program license from user space */2101 if (strncpy_from_user(license, u64_to_user_ptr(attr->license),....2161 /* plain bpf_prog allocation */2162 prog = bpf_prog_alloc(bpf_prog_size(attr->insn_cnt), GFP_USER);------------------------------------------------------------------------
f / vfree第一个 1GB seq_file 缓冲区(其中“//deleted”被越界写入),立即解除对所有 1024 个线程的阻塞;eBPF 程序被 vmalloc() 放入刚刚 vfree() 的 1GB 孔中:
4KB 1GB 4KB 1GB 4KB 2GB-----|---|-----------------|---|-----------------|---|-----------------| ... |XXX| eBPF programs |XXX| seq_file buffer |XXX| seq_file buffer |-----|---|-----------------|---|-----------------|---|-----------------|
g/ 接下来,(再次通过 userfaultfd 或 FUSE)阻止我们的一个线程(在第 12795 行)在内核 eBPF验证程序。验证其 eBPF 程序之后但在内核对其进行 JIT 编译之前:
----- -------------------------------------------------- ----------------- 12640 int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, 12641 union bpf_attr __user *uattr) 12642 { .....12795 print_verification_stats(环境);-------------------------------------------------- ----------------------
h/ 最后,用越界的“//deleted”字符串覆盖了这个 eBPF 程序的指令(再次通过我们的 2GB seq_file 缓冲区),因此使内核 eBPF 验证器的安全检查无效:
"//deleted" | 4KB v 1GB 4KB 1GB 4KB 2GB-----|---|---+-------------|---|-----------------|---|-----------------| ... |XXX| eBPF programs |XXX| seq_file buffer |XXX| seq_file buffer |-----|---|---+-------------|---|-----------------|---|-----------------| | | | | \----<----<----<----<----<----<----<----/ 8182B -2GB-10B
首先,变换这种不受控制的 eBPF 程序损坏为信息泄露。第一个未损坏的 eBPF 程序被
内核 eBPF 验证器认为是安全的、
Manfred Paul 的 btf和 map_push_elem 技术,将这种有限的越界读写转换为内核内存的任意读写:
- 通过任意内核读取,我们定位符号“__request_module”,从而找到函数__request_module(),反汇编这个函数,
并从
“if (!modprobe_path[0])”指令中提取 modprobe_path[] 的地址。
- 使用任意内核写入,我们用我们自己的可执行文件的路径覆盖modprobe_path[](默认为“/sbin/modprobe”)的内容,并调用 request_module()(通过创建一个 netlink 套接字),
它执行 modprobe_path,就是我们自己的可执行文件。
缓解措施
========================== ==========================================
重要说明:以下缓解措施仅阻止我们特定的漏洞利用(但可能存在其他漏洞利用技术);至彻底修复这个漏洞,内核必须打补丁。
- 将 /proc/sys/kernel/unprivileged_userns_clone 设置为 0,以防止攻击者在用户命名空间中挂载长目录。但是,攻击者可能会通过 FUSE 挂载一个长目录;还没有完全探索这种可能性,因为无意中在 systemd 中偶然发现了CVE-2021-33910:如果攻击者 FUSE-mount 一个长目录(超过 8MB),那么 systemd 会耗尽其堆栈,崩溃,从而导致整个操作崩溃系统(内核恐慌)。
- 将 /proc/sys/kernel/unprivileged_bpf_disabled 设置为 1,以防止攻击者将 eBPF 程序加载到内核中。然而攻击者可能会损坏其他 vmalloc() 化对象(例如,线程堆栈),但尚未调查这种可能性。