操作系统内核通常采用动态数据结构的方式管理内核堆区的元数据。例如,Linux内核使用基于加锁的链表结构描述内核堆区的位置信息。如果监控程序试图通过遍历现有内核元数据来定位内核堆上缓冲区,其操作必须遵循内核加锁和解锁规范。由于监控程序和目标操作系统中的其他内核程序可能需要同时争夺内核锁,其性能损失会比较明显。监控内核堆区的另外一种方法是以不加锁的方式访问现有内核堆区的元数据,然后定位并验证堆区守卫值。如果守卫值发生改变,则监控程序可能要求暂停目标操作系统,以确认是否为真正的缓冲区溢出。如果守卫值的变化是由于未同步造成的,监控程序将不产生报警。
为了不依赖操作系统内核中现有数据结构,在内核中引入一种新的数据结构:页身份数组(Page Identity Array,PIA)。该数据结构的基本形式是一个固定大小的数组,数组中的每一项对应一个身份页。如果页身份数组项中的内容不为空,则表明对应的内存页面已被内核堆区使用。监控程序通过查找PIA数组项中的内容找到内核堆区守卫值的位置。PIA中的第1项对应第1个物理页帧,第2项对应第2个物理页帧,依此类推,第n项对应第n个物理页帧。因为内核堆区占用的内存页面数量是固定的,所以PIA的大小可以事先确定。为了更新PIA中对应的数组项,只需要钩住(Hook)操作系统中将内存页面移入内核堆区及从内核堆区移出的函数。随后,监控程序通过遍历PIA中的每一项,定位并检查整个内核堆区的完整性。收集堆区守卫值位置信息的传统方法是截获内核程序对每一个内核缓冲区的申请和释放操作,然后将这些信息保存到动态数据结构中。与传统方法相比,本方法无须管理动态数据结构,并且减少了截获内核堆区分配和释放操作的频率,从而降低了系统的性能开销。本小节之所以使用固定大小的数据结构保存堆区元数据,是因为利用了内核堆区管理器的特性。在通用操作系统中,一个内存页面如果被内核堆区所使用,该页面将被划分成若干大小相等的缓冲区。给定某个堆区页面的首地址、该页面上第一个缓冲区的偏移及缓冲区的大小,监控程序就能确定该页面上所有的缓冲区,并找到相应的守卫值。因此,PIA中的每一项只需存储少量的元数据信息。在内存页面被移入内核堆区之前,操作系统将初始化页面上的缓冲区并更新对应的PIA数组项。监控程序通过扫描堆上缓冲区的守卫值,判断是否发生缓冲区溢出。虽然不是所有堆上缓冲区都可能被内核堆区使用,但是这种额外的读操作给系统带来的性能开销非常小。(www.xing528.com)
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。