1. 漏洞原理分析
1.1 vma缓存机制
vma就是虚拟地址区域(virtual memory area),内核用vma来管理进程分配的虚拟内存地址。vma在内核中用结构体struct vm_ares_struct 表示,定义如下:
struct vm_area_struct {
.....
unsigned long vm_start; // 起始虚拟地址
unsigned long vm_start; // 起始虚拟地址
struct vm_area_struct *vm_next, *vm_prev; //同时是一个链表节点
struct rb_node vm_rb; //同时也是一个红黑树节点
......
}
一个进程有多个vma,这些vma如何管理呢,根据结构体的定义可知vma同时存在2种组织方式
1)链表,用于遍历vma
2)红黑树,用于查找某个虚拟地址所在的vma
在内核中有个find_vma函数,根据传入的虚拟地址addr,查找vma组成的红黑树,找到这个addr所在的vma并返回,也就是vm_start <= addr <= vm_end。这个函数在内核中调用的比较频繁,比如进程访问一个虚拟地址时,在内核里面都会先去找这个地址对应的vma。 如果每次find_vma都走红黑树查找,效率会比较低。vma缓存机制,就是为了优化find_vma这个函数的执行效率,这个机制将上次find_vma得到的vma缓存下来,下次find_vma时,先去缓存中找,找不到时再去走红黑树查找。
1.2 缓存机制第一次优化
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=615d6e8756c87149f2d4c1b93d471bca002bd849
根据这个优化的patch可以看到,最原始的缓存机使用的是mm_struct中的mmap_cache,即mmap_cache指向上次find_vma函数返回的vma。但是在多线程环境下,所有线程都是共用一个mm_struct,也就是说所有线程共用一个缓存mmap_cache,那么这种情况下,缓存的命中率就不会很高。所以这个patch主要的思想就是给每个线程一个单独的缓存,互不影响。
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 290901a..2b58d19 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -342,9 +342,9 @@ struct mm_rss_stat {
struct kioctx_table;
struct mm_struct {
- struct vm_area_struct * mmap; /* list of VMAs */
+ struct vm_area_struct *mmap; /* list of VMAs */
struct rb_root mm_rb;
- struct vm_area_struct * mmap_cache; /*移除老的缓存方式*/
+ u32 vmacache_seqnum; /* per-thread vmacache */
#ifdef CONFIG_MMU
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr, unsigned long len,
mm_struct里面新增的vmacache_seqnum后面再讲作用,继续往下看补丁。由于每个线程都对应一个单独的task_struct,所以将缓存放到了task_struct里面,即vmacache,而且为task_struct结构体新增了一个u32类型的成员变量vmacache_seqnum。
@@ -1235,6 +1239,9 @@ struct task_struct {
#ifdef CONFIG_COMPAT_BRK
unsigned brk_randomized:1;
#endif
+ /* per-thread vma caching */
+ u32 vmacache_seqnum;
+ struct vm_area_struct *vmacache[VMACACHE_SIZE];
#if defined(SPLIT_RSS_COUNTING)
struct task_rss_stat rss_stat;
#endif
线程之间共用虚拟地址空间,即共用一个mm_struct,vma也都是共用的。 task_struct,mm_struct,vma_area_struct的关系如下图:
思考一个问题,如果一个vma被一个线程释放掉了,但是这个vma还在另外一个线程的缓存中,而这个线程不知道它自己缓存的vma被释放掉了,后面这个线程在find_vma的时候,如果要查找的地址刚好在这个vma中,那么就会把这个已经释放了的vma返回,这样就产生了UFA(Use After Free),所以需要一个同步机制。
继续看这个patch,在task_struct和mm_strcut这2个结构体中都新增了一个u32的成员变量vmacache_sequm,用来做同步,机制如下:
1)当有vma被某个线程给释放了,将mm_struct中的vmacache_sequm加1
2)线程在find_vma中使用缓存时,先比较task_struct和mm_struct中的vmacache_sequm,如果不相等,说明vma有变化,清空缓存,走红黑树查找vma
当一个vma被释放的时候,调用vmacache_invalidate将mm_strcut中的vmacache_seqnum加1,但是vmacache_sequm是一个u32类型的变量,如果一直加1,是有可能发生溢出的。所以在这个函数中有个检测溢出的处理,如果发生溢出,就调用vmacache_flush_all遍历所有线程将他们的vmacache都清空,然后vmacache_sequm又从0开始加1。
+static inline void vmacache_invalidate(struct mm_struct *mm)
+{
+ mm->vmacache_seqnum++;
+
+ /* 处理溢出 */
+ if (unlikely(mm->vmacache_seqnum