Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2022-2590-analysis — CVE-2022-2590에 대한 심층 분석: Linux 커널 shmem의 Dirty COW 변종으로, 근본 원인, 경쟁 조건, 개념 증명(PoC) 익스플로잇을 다룹니다. | Kitploit
도구/GitHubGitHub/moonnull/cve-2022-2590-analysis
Vulnerability AnalysisExploitationBinary Exploitation
GitHubmoonnull/cve-2022-2590-analysis

CVE-2022-2590-analysis

CVE-2022-2590에 대한 심층 분석: Linux 커널 shmem의 Dirty COW 변종으로, 근본 원인, 경쟁 조건, 개념 증명(PoC) 익스플로잇을 다룹니다.

저장소 보기
22시간 56분 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2022-2590, shmem으로 제한된 Dirty COW

리눅스 커널 버전: Linux/x86 6.0.0-rc1 (커밋 37887783b3fef877bf34b8992c9199864da4afcb)

소개:

이 취약점은 UFFDIO_CONTINUE를 사용하여 FOLL_FORCE, FOLL_COW, pte_dirty를 검사하는 can_follow_write_pte 함수를 충족시킴으로써 공격자가 읽기 전용 공유 메모리 페이지에 임의의 내용을 쓸 수 있게 합니다.

분석:

root@kitploit:~
/*
 * FOLL_FORCE can write to even unwritable pte's, but only
 * after we've gone through a COW cycle and they are dirty.
 */
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의 플래그에 쓰기 플래그(writable flag)가 포함되어 있습니다.
  2. flags 변수에 FOLL_FORCE와 FOLL_COW 플래그가 포함되어 있고, pte의 플래그에 dirty 플래그가 포함되어 있습니다.

조건 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); // __get_user_pages with FOLL_FORCE on
    		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

  • FOLL_COW

    root@kitploit:~
    /*
    * mmap_lock must be held on entry.  If @locked != NULL and *@flags
    * does not include FOLL_NOWAIT, the mmap_lock may be released.  If it
    * is, *@locked will be set to 0 and -EBUSY returned.
    */
    static int faultin_page(struct vm_area_struct *vma,
    	unsigned long address, unsigned int *flags, bool unshare,
    	int *locked)
    {
      ...
    
      /*
      * The VM_FAULT_WRITE bit tells us that do_wp_page has broken COW when
      * necessary, even if maybe_mkwrite decided not to set pte_write. We
      * can thus safely do subsequent page lookups as if they were reads.
      * But only do so when looping for pte_write is futile: in some cases
      * userspace may also be wanting to write to the gotten user page,
      * which a read fault here might prevent (a readonly page might get
      * reCOWed by userspace write).
      */
      if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))
        *flags |= FOLL_COW;
      return 0;
    }
    

    Dirty COW 패치의 일부인 faultin_page 함수에서 FOLL_COW와의 OR 연산을 사용할 수 있습니다.

    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:
     		/*
     		 * If we have a pending SIGKILL, don't keep faulting pages and
     		 * potentially allocating memory.
     		 */
     		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; // try follow page again
     			case -EBUSY:
     			case -EAGAIN:
     				ret = 0;
     				fallthrough;
     			case -EFAULT:
     			case -ENOMEM:
     			case -EHWPOISON:
     				goto out;
     			}
     			BUG();
     		} else if (PTR_ERR(page) == -EEXIST) {
        ...
     }
    

    FOLL_COW 플래그가 켜진 상태로 can_follow_write_pte 함수에 도달하려면 __get_user_pages 함수 내에서 faultin_page 함수를 호출하고, retry 레이블로 리디렉션한 다음, faultin_page 함수를 다시 호출해야 합니다.

  • pte_dirty

    이 조건은 다음 커밋으로 인해 충족될 수 있습니다: https://github.com/torvalds/linux/commit/9ae0f87d009ca6c4aab2882641ddfc319727e3db

    root@kitploit:~
    /*
    * Install PTEs, to map dst_addr (within dst_vma) to page.
    *
    * This function handles both MCOPY_ATOMIC_NORMAL and _CONTINUE for both shmem
    * and anon, and for both shared and private VMAs.
    */
    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); // set pte dirty unconditionally
     	if (page_in_cache && !vm_shared)
     		writable = false;
    
     	...
    }
    

    위 패치로 인해 읽기 전용 공유 메모리 페이지에 대한 pte를 무조건 dirty 상태로 설치할 수 있습니다.

  •  

    이 세 가지 플래그가 충족되면 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); // get read-only shared memory page
    
    	...
    
    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 및 readUFFDIO_CONTINUE ioctlpwrite
    madvise // 페이지를 zap
    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를 dirty로 만들기
    set_pte_at(dst_mm, dst_addr, dst_pte, _dst_pte); // pte 설치mem_rw
    access_remote_vm // FOLL_FORCE 사용
    __get_user_pages
    can_follow_write_pte // FOLL_COW 없음, 0 반환
    faultin_page
    flags
    retry:
    follow_page_pte
    madvise // 페이지를 zap
    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를 dirty로 만들기
    set_pte_at(dst_mm, dst_addr, dst_pte, _dst_pte); // pte 설치
    can_follow_write_pte // 1 반환
    copy_to_user(buf, page, this_len) // 읽기 전용 페이지에 내용 쓰기

    madvise 및 read 루틴은 두 번 실행됩니다. 첫 번째 실행은 __get_user_pages의 retry를 유도하고, 두 번째 실행은 mfill_atomic_install_pte에 의해 dirty로 만들어진 pte가 가리키는 페이지를 follow_page_pte가 반환하도록 유도합니다.

    PoC 및 패치:

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

    위 링크에서 David Hildenbrand의 재현 코드(reproducer)를 확인할 수 있습니다.

    그러나 해당 재현 코드는 정확한 레이스 시나리오를 파악하기 어렵게 만들기 때문에, 저는 더 선형적인 실행을 선호하도록 재현 코드를 수정했고, 이에 따라 리눅스 커널 소스 코드도 수정했습니다.

    .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 register가 실패합니다)

    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

    도구 다운로드