在本系列文章中,我们将为读者分享关于内核代码模糊测试方面的见解。
简介
对于长期关注Linux内核开发或系统调用模糊测试的读者来说,很可能早就对trinity(地址:https://lwn.net/Articles/536173/)和syzkaller(地址:https://lwn.net/Articles/677764/)并不陌生了。近年来,安全研究人员已经利用这两个工具发现了许多内核漏洞。实际上,它们的工作原理非常简单:向内核随机抛出一些系统调用,以期某些调用会导致内核崩溃,或触发内核代码中可检测的漏洞(例如缓冲区溢出漏洞)。
尽管这些Fuzzer能够对系统调用自身(以及通过系统调用可访问的代码)进行有效的模糊测试;但是,对于在用户空间和内核之间的边界上发生的事情,这两款工具却鞭长莫及。实际上,这个边界处发生的事情比我们想象的更为复杂:这里的代码是用汇编语言编写的,在内核可以安全地开始执行其C代码之前,必须对各种体系结构状态(CPU状态)进行安全检查,或者说是“消毒”。
本文将同读者一起,探索如何为x86平台上的Linux内核入口代码编写Fuzzer工具。
在继续之前,不妨先简单了解一下64位内核涉及的主要两个文件:
· entry_64.S:64位进程的入口代码。
· entry_64_compat.S:32位进程的入口代码。
总的来说,入口代码大约有1700行汇编代码(其中包括注释),所以,阅读这些代码的工作量并不算小,同时,这也只是整个内核代码中很小的一部分。
memset()示例
首先,我想给出一个从用户空间进入内核时,内核需要进行验证的CPU状态的具体例子。
在x86平台上,memset()通常是由rep stos指令实现的,因为在连续的字节范围内进行写操作方面,奇热该指令已经被CPU/微码进行了高度的优化。从概念上讲,这是一个硬件循环,它重复(rep)一个存储操作(stos)若干次;目标地址由%RDI寄存器指定,迭代次数由%RCX寄存器给出。例如,您可以使用内联汇编实现memset(),具体如下所示:
static inline void memset(void *dest, int value, size_t count) { asm volatile ("rep stosb" // 4 : "+D" (dest), "+c" (count) // 1, 2 : "a" (value) // 3 : "cc", "memory"); // 5 }
对于上述内联汇编代码来说,其作用就是告诉GCC&