避免 SYSRET 和 IRETQ 指令出现问题

在 x86-64 上,内核分别使用 SYSRETIRETQ 指令从系统调用和中断返回。我们必须小心不要在这些说明中使用非规范返回地址(至少在 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() 中设置非用户空间地址。