在 x86-64 上,内核分别使用 SYSRET
和 IRETQ
指令从系统调用和中断返回。我们必须小心不要在这些说明中使用非规范返回地址(至少在 Intel CPU 上),因为这会导致指令在内核模式下出现故障,而这不安全。相比之下,在 AMD CPU 上,如果与非规范返回地址搭配使用,SYSRET
会在用户模式下发生故障。
在内核模式下,这些指令故障的一个问题是,在 gs
寄存器从内核 x86_percpu
变量交换为受用户空间控制的值之后,这些指令会出现在中断或系统调用处理机制的末尾。当内核模式下发生异常时,gs
寄存器不会更改,因为它假定当前的 gs
寄存器属于内核。这样一来,内核就会使用由用户控制的 x86_percpu
结构来处理故障,而且很容易导致内核代码执行。
通常,最低的非负非规范地址是 0x0000800000000000
(== 1 << 47)。用户进程可能导致系统调用返回地址为非规范网址的一种方法是,将一个 4k 可执行页面映射到该地址正下方(位于 0x00007ffffffff000
处),在该页面的末尾放置一条 SYSCALL
指令,然后执行 SYSCALL
指令。
为避免此问题,请执行以下操作:
如果某个网页的虚拟地址不是规范网页,我们禁止映射该网页。
我们不允许使用
zx_thread_write_state()
将RIP
寄存器设置为非规范地址。我们禁止在
ThreadDispatcher::MakeRunnable()
中将线程入口点设置为非规范地址。我们不允许在
zx_thread_start()
和zx_process_start()
中设置非用户空间地址。