前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Hypervisor Necromancy;恢复内核保护器(2)

Hypervisor Necromancy;恢复内核保护器(2)

原创
作者头像
franket
发布2022-02-09 10:29:13
发布2022-02-09 10:29:13
2.6K00
代码可运行
举报
文章被收录于专栏:技术杂记技术杂记
运行总次数:0
代码可运行
代码语言:javascript
代码运行次数:0
运行
复制
在我们的最小实现中,我们不会启用 IRQ 或 FIQ。
此外,我们不会实施任何 EL0 应用程序或执行
`svc` 从我们的内核调用,结果所有 VBAR_EL1 条目都设置为
导致系统挂起(无限循环)。同样,对于 EL3,我们只期望
来自较低级别 AArch64 模式的同步异常。结果只有
相应的“vectors_el3”条目(+0x400)已设置,所有其他条目都会导致
系统挂起与 EL1 向量一样。异常处理程序保存当前
处理器状态(通用和状态寄存器)并调用
第二阶段处理程序。我们遵循`smc`调用约定[05],存储
W0 寄存器中的函数标识符和寄存器 X1-X6 中的参数
(即使我们只使用一个参数)。如果函数标识符是
未知,然后系统挂起,模糊测试中的一个重要决定
设置。


// framework/vectors.S

.align 11
.global vectors
vectors:
    /*
     * Current EL with SP0
     */
    .align 7
    b       .          /* Synchronous */
    .align 7
    b       .          /* IRQ/vIRQ */
    ...

.align 11
.global vectors_el3
vectors_el3:
    ...

    /*
     * Lower EL, aarch64
     */
    .align 7
    b el3_synch_low_64
...

el3_synch_low_64:
    build_exception_frame

    bl  handle_interrupt_el3

    cmp x0, #0
    b.eq 1f
    b .
1:
    restore_exception_frame
    eret
...


处理器在复位后进入 EL3,为了降低到较低的 EL,我们
必须初始化所需 EL 和控制寄存器的执行状态
并在所需的 EL 中构造一个假状态以通过“eret”返回。甚至
虽然我们将从 EL3 直接降到 EL1 以允许
专有的 EL2 实现来定义自己的状态,我们仍然必须
设置一些 EL2 状态寄存器值来初始化 EL1 执行状态。
不遵守最小配置会导致 `eret`
调用对执行异常级别没有影响(至少在
QEMU),换句话说,我们不能降到更低的 EL。

详细地说,要从 EL3 下降到 EL2,我们必须在 Secure 中定义 EL2 状态
配置寄存器 (SCR_EL3)。我们设置 SCR_EL3.NS(位 0)来指定
我们在普通世界,SCR_EL3.RW(位10)指定EL2是AArch64
以及任何需要的保留位。此外,我们将 SCR_EL3.HCE(第 8 位)设置为
在此处启用“hvc”指令,尽管这也可以在
后面的步骤。接下来,为了能够降到 EL1,我们修改 Hypervisor
配置寄存器 (HCR_EL2) 设置 HCR_EL2.RW (bit 31) 并指定
EL1 是 AArch64 和任何其他必需的保留位。尽可能接近
可能的原始设置我们在这里设置了更多的位,例如
HCR_EL2.SWIO(位 1),指示缓存失效行为。这些
我们可以通过上述预言机获得额外的价值
稍后将在文章中介绍。


// framework/boot64.S

.global _reset
_reset:
    // setup EL3 stack
    ldr x30, =stack_top_el3
    mov sp, x30

    // setup EL1 stack
    ldr x30, =stack_top_el1
    msr sp_el1, x30

    ...

    // Setup exception vectors for EL1 and EL3 (EL2 is setup by vmm)
    ldr x1, = vectors
    msr vbar_el1, x1
    ldr x1, = vectors_el3
    msr vbar_el3, x1

    ...

    // Initialize EL3 register values
    ldr   x0, =AARCH64_SCR_EL3_BOOT_VAL
    msr   scr_el3, x0

    // Initialize required EL2 register values
    mov   x0, #( AARCH64_HCR_EL2_RW )
    orr   x0, x0,#( AARCH64_HCR_EL2_SWIO )
    msr   hcr_el2, x0

    ...

    /*
     * DROP TO EL1
     */
    mov   x0, #( AARCH64_SPSR_FROM_AARCH64 | AARCH64_SPSR_MODE_EL1 | \
                 AARCH64_SPSR_SP_SEL_N)
    msr   spsr_el3, x0


    // drop to function start_el1
    adr   x0, start_el1
    msr   elr_el3, x0
    eret


对于伪造的低级状态,异常链接寄存器 (ELR_EL3) 保存
异常返回地址,因此我们将其设置为所需的函数
(`start_el1()`)。保存的进程状态寄存器 (SPSR_EL3) 保存
异常之前的处理器状态 (PSTATE) 值,因此我们设置它的值
以便假异常来自 EL1 (SPSR_EL3.M bits[3:0]),使用
SP_EL1(SPSR_EL3.M 位 0)并在 AArch64 模式下执行(SPSR_EL3.M 位 4)。
`eret` 将我们带到 EL1 中的 `start_el1()`。最终登记册相关
exceptions 是保存信息的异常综合症寄存器 (ESR_ELx)
关于异常的性质(综合症信息),因此它
对返回的 EL 没有任何价值,可以忽略。


------[ 2.1.1 - EL1

如前所述,我们的目标是提供最小的设置。考虑到这一点,
还需要尽可能接近原始设置。
我们的 EL1 配置是根据这些要求定义的,并且
实现这一点,我们使用了来自两者的系统配置寄存器值
内核源代码和将在下面介绍的 EL2 预言机
部分,但现在我们可以放心地假设这些是任意选择的
价值观。我们将介绍一些关键系统的详细信息
寄存器值,但有关详细说明,请参阅 AARM 部分
“D13.2 通用系统控制寄存器”。


start_el1:
    //初始化EL1所需的寄存器值

    ldr x0, =AARCH64_TCR_EL1_BOOT_VAL
    msr tcr_el1, x0

    ldr x0, =AARCH64_SCTLR_EL1_BOOT_VAL
    msr sctlr_el1, x0
    ...


#define AARCH64_TCR_EL1_BOOT_VAL (\
  (AARCH64_TCR_IPS_1TB << AARCH64_TCR_EL1_IPS_SHIFT)| \
  (AARCH64_TCR_TG1_4KB << AARCH64_TCR_EL1_TG1_SHIFT)| \
  (AARCH64_TCR_TSZ_512G << AARCH64_TCR_EL1_T1SZ_SHIFT)| \
  (AARCH64_TCR_TG0_4KB << AARCH64_TCR_EL1_TG0_SHIFT)| \
  (AARCH64_TCR_TSZ_512G << AARCH64_TCR_EL1_T0SZ_SHIFT)| \
  ...
  )


正如翻译控制寄存器 (TCR_EL1) 值所示,我们使用 40 位
1TB 大小的中间物理地址空间(TCR_EL1.IPS 位[34:32]),
对于 TTBR0_EL1 和 TTBR1_EL1 4kB 翻译颗粒大小 (TCR_EL1.TG1
位 [31:30] 和 TCR_EL1.TG0 [15:14] 分别)和 25 大小的偏移量
表示每个输入 VA 有 64-25=39 位或 512GB 区域
TTBRn_EL1(TCR_EL1.T1SZ 位[21:16] 和 TCR_EL1.T0SZ 位[5:0])。

通过使用 4kB 粒度,每个转换表大小为 4kB,每个条目
是一个 64 位的描述符,因此每个表有 512 个条目。所以在第 3 级,我们有
512 个条目,每个条目指向一个 4kB 页面,换句话说,我们可以映射一个 2MB
空间。同样,Level 2 有 512 个条目,每个条目指向 2MB 空间
总计 1GB 地址空间和 1 级条目指向 1GB 空间
总计有 512GB 的地址空间。在此设置中,有 39 位
输入 VA,我们不需要 0 级表,如翻译所示
图形。有关详细信息,请参阅 AARM 部分“D5.2 VMSAv8-64 地址
翻译系统”。


+---------+---------+---------+-----------+
| [38:30] | [29:21] | [20:12] |  [11:0]   | VA segmentation with
|         |         |         |           | 4kB Translation Granule
| Level 1 | Level 2 | Level 3 | Block off | 512GB input address space
+---------+---------+---------+-----------+


                                         Physical Address
                             +-------------------------+-----------+
 VA Translation              |          [39:12]        |  [11:0]   |
 demonstration with          +-------------------------+-----------+
 4kB Granule,                               ^                 ^
 512GB Input VA Space                       |                 |
 1TB IPS                                    |                 +----------+
                                            +-------------------------+  |
                                                                      |  |
                Level 1 tlb          Level 2 tlb        Level 3 tlb   |  |
    +--------> +-----------+   +--->+-----------+  +-->+-----------+  |  |
    |          |           |   |    |           |  |   |           |  |  |
    |          +-----------+   |    +-----------+  |   |           |  |  |
    |          | 1GB block |   |    | 2MB block |  |   |           |  |  |
    |          |   entry   |   |    |   entry   |  |   |           |  |  |
    |          +-----------+   |    +-----------+  |   |           |  |  |
    |          |           |   |    |           |  |   |           |  |  |
    |          +-----------+   |    |           |  |   |           |  |  |
    |      +-->+ Tbl entry +---+    |           |  |   |           |  |  |
+---+---+  |   +-----------+        +-----------+  |   |           |  |  |
| TTBRn |  |   |           |    +-->+ Tbl entry +--+   +-----------+  |  |
+---+---+  |   |           |    |   +-----------+   +->+ Pg entry  +--+  |
    ^      |   |           |    |   |           |   |  +-----------+     |
    |      |   |           |    |   |           |   |  |           |     |
    +--+   |   +-----------+    |   +-----------+   |  +-----------+     |
       |   |                    +------+            |                    |
       |   +----+ Index +----+         |         +--+        +-----------+
       |                     |         |         |           |
+----+-+-+----+---------+----+----+----+----+----+----+------+----+
|    |   |    | Level 0 | Level 1 | Level 2 | Level 3 | PA offset | VA
+----+---+----+---------+---------+---------+---------+-----------+
      [55]      [47:39]   [38:30]   [29:21]   [20:12]     [11:0]
  TTBRn Select



对于 1 级和 2 级,每个条目都可以指向下一个翻译
表级(表项)或实际物理地址(块项)
有效地结束翻译。条目类型在 bits[1:0] 中定义,
其中第 0 位标识描述符是否有效(1 表示有效
描述符)和位 1 标识类型,值 0 用于块
条目和 1 用于表条目。结果条目类型值 3 标识
表条目和值 1 块条目。1级块条目指向1GB
使用 VA 位 [29:0] 作为 PA 偏移和级别 2 的内存区域
块条目指向 2MB 区域,其中 bits[20:0] 用作偏移量。最后的
但同样重要的是,3 级翻译表只能有页条目
(类似于块条目,但描述符类型值为 3,如前所述
级别表条目)。


61          51                             11         2  1:0
+------------+-----------------------------+----------+------+ Block Entry
| Upper Attr |           ...               | Low Attr | Type | Stage 1
+------------+-----------------------------+----------+------+ Translation

| bits | Attr      | Description                  |
---------------------------------------------------
| 4:2  | AttrIndex | MAIR_EL1 index               |
| 7:6  | AP        | Access permissions           |
| 53   | PXN       | Privileged execute never     |
| 54   | (U)XN     | (Unprivileged) execute never |
                                                    Block entry attributes
| AP | EL0 Access  | EL1/2/3 Access |               for Stage 1 translation
-------------------------------------
| 00 | None        |   Read Write   |
| 01 | Read Write  |   Read Write   |
| 10 | None        |   Read Only    |
| 11 | Read Only   |   Read Only    |



61      59                                            2  1:0
+--------+--------------------------------------------+------+ Table Entry
|  Attr  |                     ...                    | Type | Stage 1
+--------+--------------------------------------------+------+ Translation

| bits  | Attr | Description                |
---------------------------------------------
| 59    | PXN  | Privileged execute never   |
| 60    | U/XN | Unprivileged execute never |
| 62:61 | AP   | Access permissions         |
                                                Table entry attributes
| AP | Effect in subsequent lookup levels |     for Stage 1 translation
-------------------------------------------
| 00 | No effect                          |
| 01 | EL0 access not permitted           |
| 10 | Write disabled                     |
| 11 | Write disabled, EL0 Read disabled  |


在我们的设置中,我们使用 2MB 区域来映射内核并创建两个映射。
首先,恒等映射(VA 等于它们映射到的 PA)
设置为 TTBR0_EL1 主要在系统从不使用转换时使用
MMU 启用它。其次,在 PA 所在的 TTBR1_EL1 映射
映射到 VA_OFFSET + PA,这意味着从 TTBR1_EL1 获取 PA
VA 或反之亦然,只需减去或添加 VA_OFFSET
相应地。这在 RKP 初始化期间很重要。


#define VA_OFFSET 0xffffff8000000000

#define __pa(x) ((uint64_t)x - VA_OFFSET)
#define __va(x) ((uint64_t)x + VA_OFFSET)


创建页表和启用 MMU 的代码大量借鉴自
Linux内核实现。我们使用一个 1 级条目和所需的
两个表相邻的 2 级块条目的数量
预分配(在链接描述文件中定义)物理页。1级
条目由宏“create_table_entry”评估。首先,入口索引是
从 VA 位 [38:30] 中提取。入口值是下一个Level table PA
与有效的表条目值进行或运算。这也隐含地定义了
表条目属性,其中 (U)XN 被禁用,访问权限 (AP)
对后续级别的查找没有影响。有关其他详细信息
关于内存属性及其对内存的分层控制
访问请参阅 AARM 部分“D5.3.3 内存属性字段”
VMSAv8-64 转换表格式描述符”。

级别 2 遵循类似的过程,但在循环中映射所有必需的
宏“create_block_map”中的 VA。入口值就是我们要映射的PA
与 AARCH64_BLOCK_DEF_FLAGS 定义的块条目属性值进行或运算。
使用的标志值表示非安全内存区域,(U/P)XN 禁用,
内存属性间接寄存器中定义的普通内存
(MAIR_EL1) 和访问权限 (AP),允许读取/写入 EL1 并且不允许
访问 EL0。与表格条目一样,详细说明请参阅
AARM 部分“D5.3.3”。最后,MAIR_ELx 作为一个表来存放
内存区域的信息/属性,读者可以参考 AARM
“B2.7 内存类型和属性”部分了解更多信息。


// framework/aarch64.h

/*
 * Block default flags for initial MMU setup
 *
 * block entry
 * attr index 4
 * NS = 0
 * AP = 0 (EL0 no access, EL1 rw)
 * (U/P)XN disabled
 */
#define AARCH64_BLOCK_DEF_FLAGS (                          \
  AARCH64_PGTBL_BLK_ENTRY                                | \
  0x4 << AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_IDX_SHIFT | \
  AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_AP_RW_ELHIGH <<    \
      AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_AP_SHIFT     | \
  AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_SH_INN_SH    <<    \
      AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_SH_SHIFT     | \
  1 << AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_AF_SHIFT      \
  )


// framework/mmu.S

__enable_mmu:
    ...

    bl __create_page_tables

    isb
    mrs x0, sctlr_el1
    orr x0, x0, #(AARCH64_SCTLR_EL1_M)
    msr sctlr_el1, x0
...

__create_page_tables:

    mov x7, AARCH64_BLOCK_DEF_FLAGS
    ...

    // x25 = swapper_pg_dir
    u/ x20 = VA_OFFSET
    mov x0, x25
    adrp x1, _text
    add x1, x1, x20
    create_table_entry x0, x1, #(LEVEL1_4K_INDEX_SHIFT), \
                        #(PGTBL_ENTRIES), x4, x5

    adrp x1, _text
    add x2, x20, x1
    adrp x3, _etext
    add x3, x3, x20
    create_block_map x0, x7, x1, x2, x3
...

.macro create_table_entry, tbl, virt, shift, ptrs, tmp1, tmp2
    lsr \tmp1, \virt, \shift
    and \tmp1, \tmp1, \ptrs - 1                // table entry index
    add \tmp2, \tbl, #PAGE_SIZE                // next page table PA
    orr \tmp2, \tmp2, #AARCH64_PGTBL_TBL_ENTRY // valid table entry
    str \tmp2, [\tbl, \tmp1, lsl #3]           // store new entry
    add \tbl, \tbl, #PAGE_SIZE                 // next level table page
.endm

.macro  create_block_map, tbl, flags, phys, start, end
    lsr     \phys, \phys, #LEVEL2_4K_INDEX_SHIFT
    lsr     \start, \start, #LEVEL2_4K_INDEX_SHIFT
    and     \start, \start, #LEVEL_4K_INDEX_MASK     // table index
    orr     \phys, \flags, \phys, lsl #LEVEL2_4K_INDEX_SHIFT // table entry
    lsr     \end, \end, #LEVEL2_4K_INDEX_SHIFT     // block entries counter
    and     \end, \end, #LEVEL_4K_INDEX_MASK       // table end index
    1:   str     \phys, [\tbl, \start, lsl #3]  // store the entry
    add     \start, \start, #1                     // next entry
    add     \phys, \phys, #LEVEL2_4K_BLK_SIZE      // next block
    cmp     \start, \end
    b.ls    1b
.endm

...


作为演示,我们为 VA 0xffffff8080000000 执行手动表遍历
应该是函数`_reset()`的TTBR1_EL1 VA。1 级表
索引 (1) 为 2,条目值为 0x8008a003,表示有效
PA 0x8008a000 处的表描述符。2 级条目索引 (2) 为 0 并且
该条目的值为 0x80000711,表示物理上的块条目
地址 0x80000000。设置 PA 偏移的剩余 VA 位为零
并且检查生成的 PA 当然是功能的开始
`_reset()`。请注意,由于我们还没有启用 MMU(如图
在接下来的指令中执行的反汇编),所有内存
使用 gdb 访问指的是 PA,这就是我们可以直接检查页面的原因
表和结果 PA。在我们的设置中,即使使用 MMU 也是如此
由于身份映射而启用,但是,这不应该被假定为
适用于每个系统。



(gdb) disas
Dump of assembler code for function __enable_mmu:
   0x00000000800401a0 <+0>:     mov     x28, x30
   0x00000000800401a4 <+4>:     adrp    x25, 0x80089000 // TTBR1_EL1
   0x00000000800401a8 <+8>:     adrp    x26, 0x8008c000
   0x00000000800401ac <+12>:    bl      0x80040058 <__create_page_tables>
=> 0x00000000800401b0 <+16>:    isb
   0x00000000800401b4 <+20>:    mrs     x0, sctlr_el1
   0x00000000800401b8 <+24>:    orr     x0, x0, #0x1
End of assembler dump.

(gdb)  p/x ((0xffffff8000000000 + 0x80000000) >> 30) & 0x1ff      /* (1) */
$19 = 0x2

(gdb)  x/gx ($TTBR1_EL1 + 2*8)
0x80089010:     0x000000008008a003

(gdb)  p/x ((0xffffff8000000000 + 0x80000000) >> 21) & 0x1ff      /* (2) */
$20 = 0x0

(gdb)  x/gx 0x000000008008a000
0x8008a000:     0x0000000080000711

(gdb)  x/10i 0x0000000080000000
   0x80000000 <_reset>: ldr     x30, 0x80040000
   0x80000004 <_reset+4>:       mov     sp, x30
   0x80000008 <_reset+8>:       mrs     x0, currentel


最后,启用 MMU 后,我们就可以启用 RKP。由于EL2
未设置异常向量表,唯一的方法是下降到
EL2 来自 EL3,就像我们对 EL1 所做的那样。我们用函数标识符调用`smc`
EL3 中断处理程序重定向到函数的 CINT_VMM_INIT
`_vmm_init_el3()`。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档