Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2022-2590-analysis — 对 CVE-2022-2590 的深入分析,这是 Linux 内核中共享内存(shmem)的一个竞态条件,可通过 userfaultfd 以及绕过 FOLL_FORCE/COW 实现对只读页面的任意写入。 | Kitploit
工具/GitHubGitHub/hyeonjun17/cve-2022-2590-analysis
漏洞分析漏洞利用论文与研究学习与教育二进制利用
GitHubhyeonjun17/cve-2022-2590-analysis

CVE-2022-2590-analysis

对 CVE-2022-2590 的深入分析,这是 Linux 内核中共享内存(shmem)的一个竞态条件,可通过 userfaultfd 以及绕过 FOLL_FORCE/COW 实现对只读页面的任意写入。

查看仓库
622年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2022-2590,受限与 shmem 的 Dirty COW

linux 内核版本:Linux/x86 6.0.0-rc1(提交 37887783b3fef877bf34b8992c9199864da4afcb)

介绍:

此漏洞允许攻击者通过满足函数 can_follow_write_pte(该函数检查 FOLL_FORCE、FOLL_COW 和 pte_dirty)的条件,使用 UFFDIO_CONTINUE 向只读共享内存页面写入任意内容。

分析:

root@kitploit:~
/*
 * FOLL_FORCE 可以写入甚至不可写的 pte,但
 * 仅在我们经过 COW 周期且 pte 为脏之后。
 */
static inline bool can_follow_write_pte(pte_t pte, unsigned int flags)
{
	return pte_write(pte) ||
		((flags & FOLL_FORCE) && (flags & FOLL_COW) && pte_dirty(pte));
}

上述函数决定了一个 pte 是否可写。

如果满足以下条件之一,则该 pte 被视为可写:

  1. pte 的标志包含可写标志。
  2. flags 变量包含 FOLL_FORCE 和 FOLL_COW 标志,并且 pte 的标志包含脏标志。

为了满足条件 2,需要设置以下三个标志:

  1. FOLL_FORCE

    root@kitploit:~
    static ssize_t mem_rw(struct file *file, char __user *buf,
    			size_t count, loff_t *ppos, int write)
    {
    	...
    
    	flags = FOLL_FORCE | (write ? FOLL_WRITE : 0);
    
    	while (count > 0) {
    		size_t this_len = min_t(size_t, count, PAGE_SIZE);
    
    		if (write && copy_from_user(page, buf, this_len)) {
    			copied = -EFAULT;
    			break;
    		}
    
    		this_len = access_remote_vm(mm, addr, page, this_len, flags); // 使用 FOLL_FORCE 调用 __get_user_pages
    		if (!this_len) {
    			if (!copied)
    				copied = -EIO;
    			break;
    		}
    
    		...
    }
    

    上述函数对 /proc/<pid>/mem 执行读/写操作。

    通过使用此函数,可以在启用 FOLL_FORCE 标志的情况下到达 __get_user_pages 函数。

    尽管 FOLL_FORCE 本应用于 ptrace,但其必要性已在以下提交中得到证明:https://github.com/torvalds/linux/commit/f511c0b17b081562dca8ac5061dfa86db4c66cc2

  2. FOLL_COW

    root@kitploit:~
    /*
    * 进入时必须持有 mmap_lock。如果 @locked != NULL 且 *@flags
    * 不包含 FOLL_NOWAIT,则可能会释放 mmap_lock。如果释放,
    * 则 *@locked 将被设置为 0 并返回 -EBUSY。
    */
    static int faultin_page(struct vm_area_struct *vma,
    	unsigned long address, unsigned int *flags, bool unshare,
    	int *locked)
    {
      ...
    
      /*
      * VM_FAULT_WRITE 位告诉我们 do_wp_page 在必要时已经破坏了 COW,
      * 即使 maybe_mkwrite 决定不设置 pte_write。因此
      * 我们可以安全地将后续的页面查找视为读取。
      * 但只应在循环查找 pte_write 无效时这样做:在某些情况下
      * 用户空间可能也想要写入获取的用户页,
      * 而这里的读故障可能会阻止这一点(只读页可能会被用户空间写入重新 COW)。
      */
      if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))
        *flags |= FOLL_COW;
      return 0;
    }
    

    可以在 faultin_page 函数中使用与 FOLL_COW 的 OR 操作,这是 Dirty COW 补丁的一部分。

    root@kitploit:~
    static long __get_user_pages(struct mm_struct *mm,
     		unsigned long start, unsigned long nr_pages,
     		unsigned int gup_flags, struct page **pages,
     		struct vm_area_struct **vmas, int *locked)
     {
     	...
     retry:
     		/*
     		 * 如果我们有待处理的 SIGKILL,不要继续故障处理页面并
     		 * 可能分配内存。
     		 */
     		if (fatal_signal_pending(current)) {
     			ret = -EINTR;
     			goto out;
     		}
     		cond_resched();
    
     		page = follow_page_mask(vma, start, foll_flags, &ctx); // can_follow_write_pte
     		if (!page || PTR_ERR(page) == -EMLINK) {
     			ret = faultin_page(vma, start, &foll_flags,
     					   PTR_ERR(page) == -EMLINK, locked); // flags |= FOLL_COW
     			switch (ret) {
     			case 0:
     				goto retry; // 再次尝试追踪页面
     			case -EBUSY:
     			case -EAGAIN:
     				ret = 0;
     				fallthrough;
     			case -EFAULT:
     			case -ENOMEM:
     			case -EHWPOISON:
     				goto out;
     			}
     			BUG();
     		} else if (PTR_ERR(page) == -EEXIST) {
        ...
     }
    
  3. pte_dirty

    由于以下提交,可以满足此条件:https://github.com/torvalds/linux/commit/9ae0f87d009ca6c4aab2882641ddfc319727e3db

    root@kitploit:~
    /*
    * 安装 PTEs,将 dst_addr(在 dst_vma 内)映射到页面。
    *
    * 此函数为 shmem 和 anon 处理 MCOPY_ATOMIC_NORMAL 和 _CONTINUE,
    * 并且为共享和私有 VMA 处理。
    */
    int mfill_atomic_install_pte(struct mm_struct *dst_mm, pmd_t *dst_pmd,
    		     struct vm_area_struct *dst_vma,
    		     unsigned long dst_addr, struct page *page,
    		     bool newly_allocated, bool wp_copy)
    {
     	...
    
     	_dst_pte = mk_pte(page, dst_vma->vm_page_prot);
     	_dst_pte = pte_mkdirty(_dst_pte); // 无条件设置 pte 为脏
     	if (page_in_cache && !vm_shared)
     		writable = false;
    
     	...
    }
    

    由于上述补丁,可以为只读共享内存页面安装一个处于脏状态的 pte。

 

如果这三个标志都满足,can_follow_write_pte 函数将返回 true。

root@kitploit:~
static struct page *follow_page_pte(struct vm_area_struct *vma,
		unsigned long address, pmd_t *pmd, unsigned int flags,
		struct dev_pagemap **pgmap)
{
	...
    
	// true && !true == false
	if ((flags & FOLL_WRITE) && !can_follow_write_pte(pte, flags)) {
		pte_unmap_unlock(ptep, ptl);
		return NULL;
	}

	page = vm_normal_page(vma, address, pte); // 获取只读共享内存页面

	...

out:
	pte_unmap_unlock(ptep, ptl);
	return page;
no_page:
	pte_unmap_unlock(ptep, ptl);
	if (!pte_none(pte))
		return NULL;
	return no_page_table(vma, flags);
}

函数 follow_page_pte 返回只读共享内存页面。

竞争场景:

以下是由 PoC 证明的竞争场景,稍后将介绍。

madvise 和 read 例程执行两次;第一次执行诱导 __get_user_pages 的 retry,第二次执行引导 follow_page_pte 返回由 mfill_atomic_install_pte 设置为脏的 pte 所指向的页面。

PoC 和补丁:

https://www.openwall.com/lists/oss-security/2022/08/15/1

您可以在上面的链接中查看 David Hildenbrand 的重现器。

然而,由于该重现器使得识别确切的竞争场景变得困难,我修改了重现器以偏向更线性的执行,并相应地修改了 Linux 内核源代码。

.config:

root@kitploit:~
...
CONFIG_USERFAULTFD=y
CONFIG_HAVE_ARCH_USERFAULTFD_WP=y
CONFIG_HAVE_ARCH_USERFAULTFD_MINOR=y
...

PoC:(poc.c)

补丁:(poc_deayzl.patch)

为了测试,必须执行以下预备步骤。(david 的重现器说明中文件路径是 /tmp/foo。但当前版本中 tmpfs 不是共享内存,因此 userfaultfd 注册会失败)

root@kitploit:~
sudo -s
echo "asdf" > /dev/shm/foo
chmod 0404 /dev/shm/foo
exit

运行 PoC 后,您将看到以下屏幕。

PoC-result

参考:

提交:https://github.com/torvalds/linux/commit/5535be3099717646781ce1540cf725965d680e7b

lore.kernel 补丁 v1:https://lore.kernel.org/linux-mm/[email protected]/#r

lore.kernel 补丁 v2:https://lore.kernel.org/all/[email protected]/T/#u

openwall:https://www.openwall.com/lists/oss-security/2022/08/08/1

openwall2:https://lists.openwall.net/linux-kernel/2022/08/08/418

下载工具

为了在启用 FOLL_COW 标志的情况下到达 can_follow_write_pte 函数,需要在 __get_user_pages 函数内调用 faultin_page 函数,重定向到 retry 标签,并再次调用 faultin_page 函数。

madvise 和 readUFFDIO_CONTINUE ioctlpwrite
madvise // 清除页面
shmem_fault // 读故障
handle_userfault
userfaultfd_continue
mcontinue_atomic_pte
ret = shmem_getpage(inode, pgoff, &page, SGP_NOALLOC); // 获取页面
mfill_atomic_install_pte
_dst_pte = pte_mkdirty(_dst_pte); // 将 pte 设置为脏
set_pte_at(dst_mm, dst_addr, dst_pte, _dst_pte); // 安装 ptemem_rw
access_remote_vm // 带有 FOLL_FORCE
__get_user_pages
can_follow_write_pte // 没有 FOLL_COW,返回 0
faultin_page
flags
retry:
follow_page_pte
madvise // 清除页面
shmem_fault // 读故障
handle_userfault
userfaultfd_continue
mcontinue_atomic_pte
ret = shmem_getpage(inode, pgoff, &page, SGP_NOALLOC); // 获取页面
mfill_atomic_install_pte
_dst_pte = pte_mkdirty(_dst_pte); // 将 pte 设置为脏
set_pte_at(dst_mm, dst_addr, dst_pte, _dst_pte); // 安装 pte
can_follow_write_pte // 返回 1
copy_to_user(buf, page, this_len) // 向只读页面写入内容