4

Preventing kernel-stack leaks

 3 years ago
source link: https://kernel.taobao.org/2018/05/Preventing-kernel-stack-leaks/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

May 6, 2018

Preventing kernel-stack leaks

本文主要介绍一种通过给编译器增加初始化栈变量指令来避免未初始化的内核栈变量导致栈内容泄露的方法,由于增加了性能损耗以及可能掩盖一些bug暂时没有进入主干。

原文链接

内核栈是每个线程的地址空间中的一个小的、频繁重用的区域。由于cache局部性原因,频繁重用这块区域可以带来良好的性能,但它也带来了一个问题:在栈上留下的上次使用后的数据没有被重新初始化从而可能导致泄露。PaX补丁包含一种机制,用于清除栈中的数据并防止泄漏。但是该系列的补丁在准备合并主干时遇到了一些障碍。

C语言在设计上就不会初始化自动变量(自动变量即当定义它们的函数被调用时在堆栈上创建的局部变量)。如果编程者不主动初始化那些自动变量,它们可能残留垃圾数据;特别是他们在函数调用完后不会被清理,从而残留在栈上。因此,如果我们没能成功初始化这些变量,将会产生许多我们不期望看到的结果。譬如将未初始化变量写入到用户空间可能会泄漏栈上的数据,而如果在函数中使用未初始化的值,则可能会产生令人惊讶的结果。如果攻击者能够找到一种方法来控制栈上留下的数据,那么他们就可以利用这种方式来攻击内核。这两种类型的漏洞在都已经在内核中出现过,将来也肯定会继续出现。

虽然大多数使用未初始化数据的问题都可以直接归咎于编码者,但情况也并非总是如此。例如,存储在栈上的数据结构可能包含填充字段,由于程序本身并不会使用该内存,因此编译器可以决定它不需要初始化这些填充字段。但是,当内核响应系统调用而写入这样的结构,未初始化的填充字段的内容可能仍然会暴露给用户空间。

解决这个问题的最佳方法是找到并修复堆栈变量没有被正确初始化的位置。像KASan这样的工具可以帮助完成这项任务,但是寻找并解决这类问题是一场永无止境的游戏。因此,最好能有一种自动防止这种类型的漏洞的方法。

一段时间以来,Alexander Popov致力于将PaX的STACKLEAK移植到Linux内核中。第9版补丁已于3月3号发布。本系列增加了一个GCC插件,用于跟踪内核堆栈的最大深度;该信息可以用来防止堆栈溢出。然而,这种跟踪的主要目的是允许内核在每次系统调用返回时清除内核栈的内容;堆栈清除代码可以通过最大深度来避免清理比实际使用更多的栈空间。根据补丁的说明,打开这个特性会导致1%的性能消耗;作为回报,内核代码将总是运行在一个堆栈内容已经被正确设置的环境中。

顺便说一下,堆栈的“清除”并不是将其设置为零。相反,而是使用一个特殊的值,这将有助于识别由未初始化的堆栈变量所导致的崩溃。

Kees Cook 评论道:“这个系列应该尽早落地“。但这是在Linus Torvalds了解到这个之前。而Torvalds并不满意,并明确表示STACKLEAK代码不太可能以目前的形式进入主流。他抱怨说:

它实际上根本就没有找出任何bug。因此,这仅仅是另一个翻篇之后被所有人遗忘的东西,并且它启用时仅仅是增加性能损耗。

他建议安全开发者应该更多地致力于发现和修复问题,从而改进内核,而不是以这样的方式来掩盖问题。

不用多说,开发者看待这个问题的观点并不一致。Cook回复道:

我认为它确实改进了内核,特别是如果我们可以通过本地编译器选项(而不仅仅是插件)获得更完整的覆盖。例如,目前堆栈内核中满是memset(),因为编译器不能正确地零初始化填充等。这是一个无尽的bug来源,这个补丁系列提供了一个全面和快速的方法来保持堆栈清除。

这个回复让Torvalds开始思考他所说的更聪明的解决问题的方法。他并不觉得简单地清除堆栈是聪明的,但是让编译器将所有自动变量初始化为零可能是。这种初始化一样可以用来防止未初始化数据的产生,但是当编译器能够确定变量已经以其他方式被正确初始化时,它可以省略。这样能显著降低开销。

在性能敏感的代码中,可以通过为那些不需要编译器初始化的变量添加一个特殊标记来减少开销,即使看起来对这些变量初始化是必要的。需要标记的地方在性能配置文件中会突出,并且这些标记本身将是可能出现未初始化数据的红色标志。

Cook赞成将这个功能添加到编译器,但他认为这还是不够的。一个新编译器需要很长时间才能被广泛采用,人们通常会在很长时间内用老的编译器来构建新内核。因此,仅仅基于编译器的方法再近几年内很难得到广泛使用。而将堆栈清除功能添加到内核中可以作为一个保护点,而不管是否使用新编译器来构建它。他还指出,有一些情况下,将自动变量赋值为零并不能覆盖所有场景。如果一个漏洞允许攻击者读取当前堆栈边界以下的数据,它可以被利用来读取可能存在于那里的有趣数据。清除堆栈也擦除可能会被无关的漏洞读取的数据,大大缩小了该漏洞可能被利用的可能性。

关于这个讨论目前还没有定论。STACKLEAK的代码在进入主线时遇到了一个大的阻碍,但是它也不应该被完全弃用。至少在短期内,对于那些不愿牺牲性能的用户,会取消堆栈清除。但确实有一些原因支持在内核中使用此特性,因此,长期来看,主线中可能会有一个STACKLEAK的位置。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK