Linux系统之越界访问

Linux系统之越界访问

Linux系统采用页式存储管理机制通过页目录和页面表将每个线性地址转换为物理地址。MMU在内存映射的过程中若遇到下面几种情况会导致映射失败,使得CPU产生一次页面出错异常(Page Fault Exception):

  • 相应的页面目录或页面表项为空,即线性地址与物理地址的映射尚未建立。
  • 相应的物理页面不在内存中。
  • 指令中规定的访问方式与页面的权限不符。

接收到缺页中断后,内核会执行预定的页面异常处理程序,使得应用程序得以从因映射失败而暂停的指令出重新恢复执行,或进行一些善后处理。

缺页异常处理函数do_page_fault在文件arch/x86/mm/fault.c中,定义如下:

dotraplinkage void do_page_fault(struct pt_regs *regs, unsigned long hw_error_code, unsigned long address)
{
    
    
	prefetchw(&current->mm->mmap_sem);
	trace_page_fault_entries(regs, hw_error_code, address);

	if (unlikely(kmmio_fault(regs, address)))
		return;

	/* Was the fault on kernel-controlled part of the address space? */
	if (unlikely(fault_in_kernel_space(address)))
		do_kern_addr_fault(regs, hw_error_code, address);
	else
		do_user_addr_fault(regs, hw_error_code, address);
}

从上述代码中可以看到,do_page_fault函数传入三个参数:中断发生时保存的寄存器数据、错误码、出错的地址。然后再根据具体的错误内容和错误原因进行相应的处理,具体的操作可以参考源码。

在缺页异常处理程序中需要确定造成缺页异常的地址是否落在某个已经建立起映射的区间,find_vma()函数可以试图在虚存空间中找出结束地址大于给定地址的第一个区间,下面分析几种常见的情况:

  • 若find_vma()找不到这样的区间,则可以确定缺页异常是由于越界访问造成的,在对当前进程的task_struct结构内的一些成分进行一些设置之后,向该进程发出一个强制的信号SIGSEGV。

进程在从中断/异常返回之前,都要检查当前进程是否有未处理的信号,对于本例中SIGSEGV信号,内核进行的处理时在屏幕上显示“Segment Fault”,然后终止进程。

  • 若find_vma()找到结束地址大于给定地址且起始地址小于给定地址的区间,则说明造成缺页异常的地址恰好落在这个区间中。这种情况下产生缺页异常的原因可能是相应的物理页面不在内存中,或者是访问权限不符。
  • 若find_vma()找到结束地址大于给定地址且起始地址小于给定地址的区间,但是其起始地址大于给定地址,这说明造成缺页异常的地址在虚拟空间中的空洞中。虚拟空间的空洞可能有两种:一种是堆栈区下面的大片未被使用的空间,另一种是由于映射区间被撤销而留下的或是在建立映射时跳过的一块地址。
    对于第一情况,若是在因为用户函数栈太小而导致在压栈的时候产生缺页异常,则需要进行堆栈扩展。对于其它的情况都会向进程发送一个SIGSEVG信号。

在i386处理器中有一条pusha指令,可以一次将32个字节压栈,所以检查的标准是%esp-32,超出这个范围的就是越界访问了。

猜你喜欢

转载自blog.csdn.net/qq_38600065/article/details/107453713