Post

探究 | Intel FRED 实现

探究 | Intel FRED 实现

概述

FRED(Flexible Return and Event Delivery) 是 Intel 引入的新型特权级切换与事件处理架构,用于替代传统的 IDT 事件交付(IDT event delivery)和 IRET 返回机制,同时 AMD 也宣布在即将到来的 Zen6 中采用此功能,所以我认为有必要借此来简单介绍一下。

在阅读本篇内容前,需要读者对 x64 架构有一定的了解。

枚举

功能支持描述
FREDCPUID.(EAX=07H, ECX=1H):EAX.FRED [Bit 17]支持 FRED 指令和寄存器
LKGSCPUID.(EAX=07H, ECX=1H):EAX.LKGS [Bit 18]支持 LKGS 指令

这是两个独立的功能,任何支持 FRED 的处理器都将支持 LKGS。

启用

操作系统可以通过设置 CR4.FRED[bit 32] 来启用 FRED,启用后,传统 IDTSYSCALLSYSENTER 事件,都将统一转换成 FRED 事件。

开启与否,并不会影响 LKGS 指令,也不会影响 RDMSRWRMSR 对于 FRED MSR 的访问。

FRED 仅适用于 64 位操作系统(IA-32e 模式),而 AMD 在设计之初取消了 IA-32e 模式下的 SYSENTER 指令,所以猜测未来 FRED 事件中也不会存在 SYSENTER。

MSR

FRED MSR地址功能
IA32_FRED_CONFIG1D4H配置 FRED 的功能
IA32_FRED_STKLVLS1D0H异常向量各自的最低栈级别
IA32_FRED_RSPn1CCH - 1CFHn= 0 - 3,各栈级别对应的 RSP
IA32_FRED_SSPn1D1H - 1D3Hn= 1 - 3,各栈级别对应的 SSP
IA32_FRED_SSP06A4H复用了原本 KCET.SS 的 IA32_PL0_SSP
  • IA32_FRED_CONFIG
    • Bits 1:0:当前栈级别(CSL)。

    • Bit 3:设置后,如果不进行栈切换,则将 SSP 递减 8。

    • Bits 8:6:不进行栈切换时 RSP 的递减量。

    • Bits 10:9CPL = 0 时可屏蔽中断的栈级别。

    • Bits 63:12:事件处理入口,要求 4K 对齐。

  • IA32_FRED_STKLVLS
    • 代表的是 32 个异常向量在 CPL = 0 时使用的栈级别,每个向量占 2 位。
  • IA32_FRED_RSPn
    • 代表的是每个栈级别对应的 RSP
  • IA32_FRED_SSPn
    • 代表的是每个栈级别对应的 SSP

事件的交付

首先,如果事件发生在 CPL = 3,则根据 IA32_STAR [47:32] 设置新的 CSSS,然后交换 GS.BaseIA32_KERNEL_GS_BASE

下一步为设置新的 RIP

RIP来源描述
IA32_FRED_CONFIG & ~FFFHCPL = 3使用 ERETU(返回用户态)
(IA32_FRED_CONFIG & ~FFFH) + 256CPL = 0使用 ERETS(返回内核态)

RIP 不符合分页标准,则先后尝试通过 #GP#DF 产生 VM Exit,如果都没能产生 VM Exit 则会产生 Triple fault。 设置好 RIP 以后,会将 RFLAGS 设置为 2,随后设置 RSPSSPCSL 进行栈切换。


传统 IDT 的栈切换是依赖于 TSS 的,当中断或异常发生时,会根据以下因素决定是否进行栈切换:

  • 如果中断 / 异常向量对应的 IDT EntryIST 非零,则会无条件将 RSP 切换为 TSSIST 字段对应的值。
  • 否则,如果存在权限跃迁,就将 RSP 切换为 TSSRSP 字段对应的值。
  • 否则,不会进行栈切换。

而当开启 FRED 以后,栈切换方式会随之改变,并引入 栈级别 这个概念,FRED 事件发生时,首先会根据事件类型和 CPL 来确定 eventSL

场景eventSL
CPL = 3,既不是嵌套异常,也不是双重异常0
CPL = 3,是嵌套异常,或是双重异常IA32_FRED_STKLVLS[2v+1:2v]
CPL = 0,异常 / NMIIA32_FRED_STKLVLS[2v+1:2v]
CPL = 0,外部中断IA32_FRED_CONFIG[10:9]
CPL = 0,INT n / SYSCALL / SYSENTER0

随后将 CSL 设置为 MAX(CSL, eventSL),并根据以下因素决定是否进行栈切换:

  • 如果事件发生在 CPL = 3,或 CSL 产生了变化,则将 RSP 切换为对应的 IA32_FRED_RSP,如果当前启用了 KCET,则同时将 SSP 切换为对应的 IA32_FRED_SSP
  • 否则,不进行栈切换,但会根据 IA32_FRED_CONFIG 中的 [8:6][3] 的配置来递减 RSPSSP。手册并未提及这些字段的具体用途,猜测是在为 Red Zone 预留空间。

指令的变动

启用 FRED 后,某些指令的行为则会发生变动,以下指令会被禁用,尝试执行会产生 #UD 异常:

  • CLRSSBSY,SETSSBSY:这两条指令将随着 Supervisor Shadow Stack Tokens 一起废除。
  • SYSEXIT,SYSRET:改为使用 ERETUERETS 处理 SYSENTERSYSCALL
  • SWAPGS:如果 FRED 转换的过程中 CPL 产生变化,处理器会自动交换 GS.Base

并且调用门也随着 IDT 一起被废除,FRED 转换将成为唯一可以修改 CPL 的方式,当 far CALLfar JMPfar RETIRET 试图修改 CPL 的时候,将产生 #GP 异常。

内核的实现

闲下来再写。

This post is licensed under CC BY 4.0 by the author.