概述
文件系统是对一个存储设备上的数据和元数据进行组织的机制,其目的是易于实现数据的查询和存取。Linux文件系统接口实现为分层的体系结构,将用户接口层、文件系统实现和操作存储设备的驱动程序分开。它是任何操作系统中最重要的功能,并且存在于所有主要的Linux操作系统中。
影响
成功利用此漏洞,可使任何非特权(本地)用户在易受攻击的主机上获得root权限。Qualys研究人员已经能够独立验证该漏洞,开发多种利用方式,并在Ubuntu 20.04、Ubuntu 20.10、Ubuntu 21.04、Debian 11和Fedora 34 Workstation上获得的完整的root用户权限。其他Linux发行版也可能会被利用。以下是该漏洞的概念验证(PoC)视频:
技术细节
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(在x86_64上是一个无符号的64位整数),并且在此乘法溢出整数m->size之前,系统将耗尽内存。不幸的是,这个size_t也会被传递给size参数是int(有符号的32位整数)而不是size_t的函数。例如,在第227行为格式化/proc/self/mountinfo中的记录而调用的show_mountinfo()函数,将在第150行调用seq_dentry(),seq_dentry()将在第530行调用dentry_path(),最终dentry_path()将在第387行调用prepend():
135 static int show_mountinfo(struct seq_file *m, struct vfsmount *mnt)
136 {
…
150 seq_dentry(m, mnt->mnt_root, " tn");
------------------------------------------------------------------------
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的深层目录结构,并且如果攻击者打开和阅读/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()分配的缓冲区下方的-2GB-10B偏移量(第16行),并且10字节的字符串“//deleted”将被越界写入(第17行)。
缓解措施
鉴于鉴于上述漏洞影响范围大,潜在危害程度高,建议用户立即安装补丁以免受此漏洞导致的风险。
END
本文始发于微信公众号(SecTr安全团队):Linux文件系统层中的本地提权漏洞(CVE-2021-33909)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论