欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

TLB 缓存延迟刷新漏洞 CVE-2018-18281 解析

来源:本站整理 作者:佚名 时间:2019-03-15 TAG: 我要投稿

简介
最近, 业内发现了一批内存管理系统的漏洞, project 0 的 Jann Horn 放出了其中一个漏洞 CVE-2018-18281 的 writeup, CVE-2018-18281 是一个 linux kernel 的通用漏洞, 这个漏洞的模式比较罕见, 不同于常规的内存溢出类漏洞, 也不是常见的 UAF 漏洞, 它是由内存管理系统的底层逻辑错误导致的, 根本原因是 TLB 缓存没有及时刷新造成虚拟地址复用, 可以实现较为稳定的提权利用.
 
TLB
linux 内核通过 多级页表 实现虚拟内存机制, 为了提高访问速度, 一些映射信息会被缓存在 TLB 里, cpu 在访问一个虚拟地址的时候, 会先查找 TLB , 如果没有命中, 才去遍历主存里的多级页表, 并将查找到的映射关系填入 TLB
反过来, 如果某个映射关系要解除, 除了在主存里的相关表项要删除, 还需要对多个cpu core 同步执行 TLB 刷新, 使得在所有 TLB 缓存里该映射关系消除, 否则就会出现不一致.
上述关于 TLB 和内存映射的说明只是简化版本, 用于简单理解这个漏洞的原因, 真正的实现不同操作系统, 不同体系架构, 都不一样. 可以查阅芯片手册, 如 TLBs, Paging-Structure Caches, and Their Invalidation 和一些分析, 如 Reverse Engineering Hardware Page Table Caches
 
漏洞
先看两个系统调用
mremap 系统调用用来改变虚拟内存的映射区域
ftruncate 系统调用用来改变文件的大小到指定大小
这两个系统调用表面上看八竿子打不着, 但在 linux 内核的实现里, 他们的调用链条会出现一个竞态条件异常
1) sys_mremap() -> mremap_to()->move_vma()->move_page_tables().
move_page_tables() first calls move_ptes() in a loop,
then performs a TLB flush with flush_tlb_range().
2) sys_ftruncate()->do_sys_ftruncate()->do_truncate()->notify_change()
->shmem_setattr()->unmap_mapping_range()->unmap_mapping_range_tree()
->unmap_mapping_range_vma() ->zap_page_range_single()->unmap_single_vma()
->unmap_page_range()->zap_pud_range()->zap_pmd_range()->zap_pte_range()
can concurrently access the page tables of a process that is in move_page_tables(),
between the move_ptes() loop and the TLB flush.
mremap 底层实现主要是 move_ptes 函数
89 static void move_ptes(struct vm_area_struct *vma, pmd_t *old_pmd,
90                 unsigned long old_addr, unsigned long old_end,
91                 struct vm_area_struct *new_vma, pmd_t *new_pmd,
92                 unsigned long new_addr, bool need_rmap_locks)
93 {
94         struct address_space *mapping = NULL;
95         struct anon_vma *anon_vma = NULL;
96         struct mm_struct *mm = vma->vm_mm;
97         pte_t *old_pte, *new_pte, pte;
98         spinlock_t *old_ptl, *new_ptl;
======================== skip ======================
133         old_pte = pte_offset_map_lock(mm, old_pmd, old_addr, &old_ptl);
134         new_pte = pte_offset_map(new_pmd, new_addr);
135         new_ptl = pte_lockptr(mm, new_pmd);
136         if (new_ptl != old_ptl)
137                 spin_lock_nested(new_ptl, SINGLE_DEPTH_NESTING);
138         arch_enter_lazy_mmu_mode();
139
140         for (; old_addr 141                                    new_pte++, new_addr += PAGE_SIZE) {
142                 if (pte_none(*old_pte))
143                         continue;
144                 pte = ptep_get_and_clear(mm, old_addr, old_pte);
145                 pte = move_pte(pte, new_vma->vm_page_prot, old_addr, new_addr);
146                 pte = move_soft_dirty_pte(pte);
147                 set_pte_at(mm, new_addr, new_pte, pte);
148         }

[1] [2] [3] [4] [5] [6]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载