内容简介:全文完极度欢迎将文章分享到朋友圈长按下面二维码关注:Linux News搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~
Avoiding page reference-count overflows
By Jonathan Corbet, April 16, 2019
题图: Photo by Saksham Choudhary from Pexels
Linux kernel 5.1-rc5的公布邮件里面提到,rc5相对于rc4的改动很散,到处都有,也列举了一些主要改动的模块。不过有一个改动没有提到,是有4个patch用来修正核心的内存管理系统里的一个安全问题。这个安全漏洞非常难以利用,所以一般来说不需要急着塞进kernel的代码里,不过这个漏洞很值得拿出来解析一下。
在著名的struct page结构里面,有非常多的内容,其中之一是_refcount,这是一个atomic_t类型的变量,统计这个page有多少个reference(引用)。atomic_t类型其实就是一个有符号的32bit数,只要_refcount不是0,就说明有对这个page的reference存在,不能把它挪作他用;当_refcount变成0的时候,这个page就能被free了。
跟kernel里的其他引用计数一样,_refcount操作需要很小心,防止溢出。看get_page()函数的实现,可以看到如下检查:
VM_BUG_ON_PAGE(page_ref_count(page) <= 0, page);
这里如果_refcount超过32bit sign类型能支持的最大的正整数(2147483647)的话,就会变成相应的绝对值对大的负数(-2147483648)。这种情况下就可能发生一些无法预测的行为了。所以编译器可能就会把它设成0xdeadbeef然后搞不好会擦除硬盘了(译者:这里在开玩笑)。所以正常的compiler都不希望它溢出变成负数,因此就加了上面这个check。不过这个check条件里面有几点值得注意:首先,这个check是在增加_refcount之前,因此这个check只有在溢出之后的下一次才会报出问题。这个还好,因为系统的行为仍然是正常的,哪怕reference count变成负数也没关系,只要别让_refcount一直继续增长回0这种情况发生。第二点是VM_BUG_ON_PAGE()只会在CONFIG_DEBUG_VM的kernel里面触发panic,而正常生产环境里用的kernel不带CONFIG_DEBUG_VM,因此什么事情都不会发生。也就是说大多数系统上,page的reference count溢出其实根本不会报出来。这就有点麻烦了:如果_refcount从负数一直增长到0了,这个page就会被free掉了,而其实有大量的用户在使用这个page。这样就会发生各种use-after-free的问题了,肯定没什么好结果。
当然据说内存管理系统的开发者不太担心这个问题也是有道理的,因为要把_refcount溢出后再增长到0,这会需要有40亿个引用才能达到目的,这个条件太难达到了。不过,Jann Horn就想办法达成了这个成就(不过确实很难):想要有超过40亿个reference的话,至少需要32GB的kernel memory,来容纳那些指向这个page的指针,此外还需要一些辅助信息的存储空间,总共Jann用了140GB的memory,和一个特制的文件系统负责让所有的read操作pending(这样就能确保不要释放page _refcount,一直增加)。
一般来说攻击者都不太会拿到这么好的条件来进行攻击。不过bug就是bug,开发者既然知道了Horn的攻击方式,就得要fix这个问题。所以接下来Linus Torvalds和Matthew Wilcox就提交了这4个patch来让此漏洞更加难以利用。
上面也提到攻击者用了一个特制的文件系统,这是一个必备条件,否则攻击者很难通过在user space的各种操作来让一个page被引用那么多次。例如只是用mmap()或者fork()操作来创建各种mapping增加page的_refcount的话,会很早就触及到其他的一些limit限制,肯定轮不到_refcount的溢出。一种可行的攻击方法是创建大量的direct-I/O requests,它们每一个都会形成一个新的reference。如果文件系统特别特别慢,比VFAT还慢(译者:这里又是傲娇的在黑Microsoft吗?),这些direct-I/O操作一直无法完成,最终_refcount可能真会溢出再回到0。
因此要防止这种攻击,第一步就是创建下面这个macro,来检查某个reference count是否快要到负数了:
#define page_ref_zero_or_close_to_overflow(page) \ ((unsigned int) page_ref_count(page) + 127u <= 127u)
这样之前提到的VM_BUG_ON_PAGE()就会利用这个macro来做检查,就能够提前发现问题了。不过有人提出,如果被攻击的目标机上CONFIG_DEBUG_VM是打开的,那么攻击者就可以用这种创造_refcount溢出的方式来构造一个denial-of-service攻击(让server发生panic)。于是Torvalds又创造了一个try_get_page()函数,这个函数里面发现_refcount快要溢出的时候就不会继续增长_refcount了。这样get_user_pages()就应该用try_get_page()函数,在无法继续增长_refcount的情况下就直接返回出错,中断分配page的动作。这样改动过之后,direct-I/O request的攻击,最后就只会返回出错信息,攻击者就得另辟蹊径了。 还有一种利用pipe的攻击方式,也被Wilcox提出patch采用类似方式来堵住了。
虽然这组patch是在5.1 release cycle的后期加进去的,不过大家不太担心这会引入新的风险。其实在一月份的时候就在一个封闭的kernel security list里面有讨论了,不过当时开发者们忽视了这个问题一段时间。所以现在的改动也算是弥补了当时大家的小错误吧,这是一件好事。没人知道会不会有哪位天才攻击者又想出什么更简单的方法来让page的_refcount溢出,所以5.1-rc5合入的这个patch能尽快到位,让大家减少一个所面对的风险吧。
全文完
极度欢迎将文章分享到朋友圈
长按下面二维码关注:Linux News搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~
以上所述就是小编给大家介绍的《LWN: 预防page的_refcount溢出攻击》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。