Part 1. 机密计算与 TEE 技术入门知识整理

本节参考文献参见 1.5 节。

1.0 Background

为了防止未经授权的访问,数据安全性是基于三种方面构建的,即静态存储数据、传输中数据、和使用中数据。

业界目睹了几项引人注目的内存抓取(例如 Target 信用卡和个人信息泄露事件)和 CPU 侧信道攻击,还有许多虚拟机管理程序(Hypervisor)漏洞也被报道出来,这些攻击和漏洞的报道显著提高了业界对 “使用中数据”的关注,另外,涉及恶意软件注入的著名攻击事件,例如 Triton 攻击和乌克兰电网袭击,更是使得保护“正在使用的数据”成为数据安全领域迫在眉睫的努力方向。

机密计算就是针对数据在使用过程中的安全问题所提出的一种解决方案。它是一种基于硬件的技术,将数据、特定功能、应用程序,同操作系统、系统管理程序或虚拟机管理器以及其他特定进程隔离开来,让数据存储在受信任的执行环境(TEE)中,即使是使用调试器,也无法从外部查看数据或者执行操作。TEE 确保只有经过授权的代码才能访问数据,如果代码被篡改,TEE 将阻止其继续进行操作。

为什么机密计算需要以硬件为基础?

由于计算架构中任何层级中的安全性都可能因为基础层的漏洞而功亏一篑,任何计算层级的安全性取决在其之下层级的安全性。这个共识推动了对最低层安全解决方案的需求,直至硬件的硅组件。

通过为最低层硬件提供安全性,最大程度降低了对整个计算架构参与方依赖性,可以从所需受信任方列表中删除操作系统和设备驱动程序供应商,平台和外围设备供应商以及服务提供商及其系统管理员,从而减少在系统生命周期中任何时刻遭受潜在危害的风险。

1.1 Basic Concepts

补充:GP,2010 年 7 月 GP(Global Platform,全球平台组织)提出了 TEE 可信执行环境的设计,他们设计的 API 被称为 GlobalPlatform API。该组织致力于跨行业协作,识别、开发和发布规范,以促进在安全芯片技术上安全且可互操作地部署和管理多个嵌入式应用程序。

先以 ARM 的 TrustZone 为例。随着智能手机的普及,手机上数据的价值越来越高,如电子支付密码(包括传统密码、指纹、人脸),带版权信息的数据等。为了进一步保护这些数据的安全,ARM 提出了 TrustZone 技术,其原理是将 CPU 的工作状态和其它相关硬件资源(中断、内存、外设和 cache 等)划分为安全(secure)和非安全(normal)两种类型,来达到数据隔离与保护。

当 CPU 运行在 normal 状态时,将只能访问 non secure 空间的资源,而不能访问 secure 资源。当 CPU 运行在 secure 状态时,既能访问 non secure 资源,也能访问 secure 资源;

然后在 ARM 上,基于 Trustzone 技术实现了 TEE ,与之对应的是 REE,一般称 TEE 为 Secure World,REE 为 Normal World。OP-TEE(开源的 TEE OS)运行中 Secure World 里边,普通 OS(如 Linux,AOSP 等)运行在 Normal World 里边;

TEE 的特点:

  • 受硬件保护机制:TEE 隔离于 REE,只通过特定的入口于 TEE 通信,并不规定某一种硬件实现方法;
  • 高性能:TEE 运行时使用 CPU 的全部性能;
  • 快速通信机制:TEE 可以访问 REE 的内存,REE 无法访问受硬件保护的 TEE 内存;

非安全操作系统在 TEE 规范中被称为富执行环境 (REE)。它通常是 Linux 操作系统的一个变种,例如 GNU/Linux 发行版或 AOSP。

TEE 的设计在 ARM 架构上主要依靠 TrustZone 技术作为底层硬件隔离机制。然而,它的结构也使其能够与任何符合 TEE 概念和目标的隔离技术兼容,例如以虚拟机形式运行或在专用 CPU 上运行。

TEE 的主要设计目标是:

  • 隔离:TEE 提供与非安全操作系统的隔离,并使用底层硬件支持保护已加载的可信应用程序 (TA) 彼此隔离;
  • 占用空间小:TEE 应保持足够小,以便驻留在合理数量的片上内存中,就像基于 ARM 的系统一样;
  • 可移植性:TEE 旨在轻松插入到不同的架构和可用的硬件,并且必须支持各种设置,例如多个客户端操作系统或多个 TEE。

研究者提供了三个类别用于比较不同的 TEE 实现,即功能性,安全性和可部署性

  • 功能标准:包括受保护的执行,密封存储,受保护的输入,受保护的输出和验证等环节,总体而言,这些标准衡量了整个使用周期中数据的物理保护,即接收,输入,处理,输出和验证;
  • 安全标准:包括数据隔离,信息流控制,清理,损害限制。此类别更侧重于避免对系统进行特定攻击的机制;
  • 可部署性:标准衡量了采用的障碍,包括对现有系统的支持,成本,开销和 SEE(Secure Execution Environment)性能;

由于功能和安全性标准之间存在一些重叠,只需要关注重点部分。

1.2 启动流程?

根据 GP 标准,TEE 的启动流程只在系统启动时执行一次,要求:

  • 启动流程至少建立一个信任根 (RoT),需要一些机制和方法去实现;
  • 一般情况下启动基于 ROM 代码:允许其他实现、依次验证加载的代码;
  • 一般情况下 TEE 首先启动:阻止 REE 接口生效。 Trusted OS 首先启动,再启动 REE;

1.3 主流的基于硬件 TEE 技术

1.3.1 ARM TrustZone

架构:

  • 每个物理的处理器内核提供了两个虚拟内核:一个被认为是非安全的,被称为“非安全区”(Normal World),另一个被认为是安全的,被称为“安全区”(Secure World);
  • 以及一个在两者之间的上下文切换机制,称为监视模式(EL3);
  • 硬件支持:ARM-v8 本身支持名为 secure mode 的模式,用来区分 normal mode。通过设置 Secure Configuration Register 系统寄存器来使能该模式的支持,该寄存器的最后 1 bit 为 0 的话,则表示当前 CPU 处于为 secure mode(注意和特权级不一样);

调用方式(最简单的情况下):

当非安全区的用户模式需要获取安全区的服务时,

  1. 首先需要进入到非安全区的特权模式(Normal World 的 Rich OS 内核态);
  2. 在该模式下调用 SMC(System Monitor Call),处理器将进入到监视模式;
  3. 监视模式备份非安全区的上下文,然后进入到安全区的特权模式,此时的运行环境是安全区的执行环境;
  4. 此后进入到安全区的用户模式,执行相应的安全服务;
  5. 原路返回;

上述 Normal World 和 Secure World 的区分可以是物理的,也可以是虚拟的。

物理和安全处理器以时间分割的方式共享物理处理器核心(分时复用?中断、调度是如何保证的?),这给了两个区域一个幻想,即它完全拥有处理器;

这也给 Secure World 一个机会,使构建隔离的可编程环境成为可能,该环境可以运行各种安全应用程序。

1.3.2 Intel SGX (Software Guard eXtensions)

  • 是对 Intel 架构的扩展。在原有架构上增加了一组新的指令集和内存访问机制;这些扩展允许应用程序实现一个被称为安全区(enclave)的容器,在应用程序的地址空间(用户态)中划分出一块被保护的区域,为容器内的代码和数据提供机密性和完整性的保护;

  • 这块有限大小的加密内存区域称为 Enclave Page Cache(EPC),支持 32MB,64MB 或 128MB (SGX2 可支持 256MB);

  • SGX 通过硬件访问控制机制来保护 Enclave 的内存。容器中的信息不会被特殊权限的恶意软件的破坏,即使底层的高特权系统软件(例如 OS 或虚拟机管理程序)是恶意的或已被破坏,SGX 仍可抵抗物理内存访问类的攻击;

    • 从外到内访问(非法):Page Fault;
    • 从内到外访问(合法):由操作系统内存管理策略控制,而且 Enclave 只能在 Ring 3(用户态)请求系统调用;
  • SGX 的实现需要处理器、内存管理部件、BIOS、驱动程序、运行时环境等软硬件协同完成。除了提供内存隔离与保护安全属性,SGX 架构还支持远程认证和密封的功能,可用于安全软件应用和交互协议的设计;

  • SGX 安全模型:

    • 可信计算基(Trusted Computing Base,TCB)被视为 CPU 组件,而系统的其他部分则被视为不可信任;
    • 每个 SGX 应用程序至少包含两个不同的部分。 位于 Secure World 内部并在 Enclave Page Cache(EPC)中执行的受信任代码,以及位于不受信任的系统内存中并执行的不受信任的代码;
    • 不可信任的部分创建 Enclave,并且定义 entry point,然后执行放置在 Enclave Page Cache 的、加密且受信任的内存中的 Secure World 程序;
    • Secure World 初始化后,不受信任的代码通过调用 EENTER 指令来调用 Enclave 代码,该指令将处理器模式从保护模式切换到安全区模式;然后处理器在 Enclave 内执行被调用的代码。调用 EEXIT 指令会导致 Enclave 内执行线程退出 Enclave,并且执行流程返回到不受信任的代码;
  • 除了用户创建的 Enclave 外,SGX 还使用了一些基础架构 Enclave(例如 Reference Enclave 和 Configuration Enclave)为本地或远程验证机制提供支持;

  • SGX 提供了一个可以保护静态 Enclave 数据的 Enclave 密封机制,可以在系统内存和 EPC 之间安全封送数据,并且使用硬件内存加密引擎(MEE)来对数据进行加密和解密。

  • SGX 支持 Enclave 内部的多线程处理:每个 APP 都可以有自己独立的 TEE,甚至可以创建多个 TEE Enclave;

1.3.3 AMD SEV (Secure Encrypted Virtualization)

  • 内存加密方案。对整个操作系统进行内存加密,操作系统本身在 TCB(Trust Computing Base)中。可以防止通过总线/ DRAM 遭受物理攻击;

  • 通过提供加密的 VM 隔离来解决高特权系统软件类别的攻击,每个虚拟机使用一个密钥隔离客户机系统和虚拟机管理程序;

  • 密钥由 AMD 安全处理器生成和管理,内存控制器中嵌入了AES-128 加密引擎。 提供适当的密钥后,将自动对主存储器(memory)中的数据进行加密和解密;

  • 系统管理程序(Hypervisor)更改通过使用硬件虚拟化指令以及与 AMD 安全处理器的通信来管理内存控制器中相应的密钥。也就是说系统组件(比如系统管理程序)试图读取客户机的内存时,只能看到被加密后的字节

  • AMD SEV VM 使用客户机中页表中的加密比特 C 来控制一个内存页是私密的还是共享的,比特 C 的位置有具体实现定义,可以是物理地址的最高位。

    • VM 标记共享(非加密)内存页时,C bit = 0,表明不必使用该 VM 的内存加密密钥对其加密;

    • 私密(加密)内存页只能用于 VM,标记 C bit = 1。一个典型的 VM 中,大多数内存页被标记为私密的,只有那些与外部通信的内存页才会标记为共享的;

  • 2017 年 AMD 又引入了 SEV-ES(Encrypted State)增加了对 CPU 寄存器状态的保护,当 VM 停止运行时,将加密所有 CPU 寄存器的内容;

    这样可以防止将 CPU 寄存器中的信息泄露给虚拟机管理程序之类的组件,甚至可以检测到对 CPU 寄存器状态的恶意修改。

  • AMD 又推出了 SEV-SNP。其建立在原始的 AMD SEV 和 SEV-ES 的基础上,可提供额外的基于硬件的内存完整性保护,以抵御基于管理程序的攻击,比如:数据回放、内存重新映射等。

    • 基本原理:如果 VM 能够读取内存的私有(加密)页面,则在它下次操作前必须始终只能读到最后一次写入的值。这意味着,如果 VM 将值 A 写入内存位置 X ,每当以后读取 X 时,它要么必须看到值 A,要么必须得到一个异常,指示无法读取内存。
  • 该方案多用于云中的应用,可保护数据免受 CSP(Cloud Solution Provider)的侵害;

1.3.4 Apple SEP (Secure Enclave Processor)

  • 使用一个独立于主处理器外的安全协处理器,其中包括基于硬件的密钥管理器,可提供额外的安全性保护。

    • 安全隔离区处理器是在片上系统(SoC)内制造的协处理器;它使用加密的内存,并包括一个硬件随机数生成器;
    • 安全协处理器提供了用于数据保护密钥管理的所有加密操作,即使内核受到威胁,也可以维护数据保护的完整性;

  • 安全隔区是集成到 Apple 片上系统 (SoC) 的专用安全子系统,由安全隔区处理器提供主要计算能力;

  • 安全隔区与应用程序处理器之间的通信受到严格的控制:将其隔离到一个中断驱动的邮箱以及共享的内存数据缓冲区;

  • 安全隔区包括一个专用的安全隔区 Boot ROM。与应用程序处理器 Boot ROM 类似,安全隔区 Boot ROM 也是不可更改的代码,用于为安全隔区建立硬件信任根;

1.3.5 小结

  • ARM TrustZone,利用硬件监督模式(EL3),使用指令或中断在同一处理器上的执行环境之间切换,像更高一级 “OS” 在 Secure World(Rich OS)和 Normal World(TEE OS)间调度和切换;

  • Intel SGX,用户态安全 Enclave,对特定区域的内存进行加密保护,在一个用户态进程内形成安全容器,对其中从 RAM 进出的内存数据进行加密,TCB 的规模最小(只有硬件和这个 Enclave),可以使用原本的系统调用(因为用户态),初始代码是从普通空间复制而来,不是加密信息;

    这个方案只有远程校验才能从外部加载/存储密钥;

  • AMD SEV,对整个 OS 内存加密,OS 本身就在 TCB 中,可以防止通过总线/ DRAM 遭受物理攻击,当客户机从租户获得密钥时,加密可以扩展到虚拟化环境;

  • Apple SEP,使用协处理器方案,即芯片组或 SoC 中内置协处理器,内部带有单独的操作系统和应用程序,应用通过安全信道与外部通信,通常包括加密引擎;

1.4 GP API

GlobalPlatform 组织提供的 TEE API 规定了大多数适用场景所需的方法。一般情况下,REE 侧构成:

  • CA(Client APP)对应一些上层应用,比如指纹采集、支付应用等,通过调用 TEE Client API 实现与 TEE 环境的交互;
  • REE Communication Agent 为 TA 和 CA 之间的消息传递提供了 REE 支持;
  • TEE Client API 是 REE 中的 TEE 驱动程序提供给外部的接口,可以使运行在 REE 中的 CA 能够与运行在 TEE 中的 TA 交换数据。

TEE 侧构成:

  • TA(Trusted Application)是 TEE 中完成特定功能的应用;
  • TEE Communication Agent 是可信操作系统的特殊组成部分,它与 REE Communication Agent 一起工作,使 TA 与 CA 之间安全地传输消息;
  • TEE Internal Core API 是 TEE 操作系统提供给 TA 调用的内部接口,包括密码学算法,内存管理等功能;
  • Trusted Device Drivers 可信设备驱动程序,为专用于 TEE 的可信外设提供通信接口;
  • Shared Memory 是一块只有 CA 和 TA 可以访问的一块安全内存,CA 和 TA 通过共享内存来快速有效传输指令和数据;

CA 常用接口:

  • TEEC_InitializeContext:对变量 Context 进行初始化配置,用来建立 CA 和 TEE 的联系,向 TEE 申请共享内存地址用于存放数据;
  • TEEC_OpenSession:建立一个 CA 和 TA 间的 session,用于 CA 和 UUID 指定的 TA 进行通信,是 CA 连接 TA 的起始点;
  • TEEC_InvokeCommand:依靠打开的 session,将传送命令请求给 TA,并将必要的指令执行参数一并发送给 TA;
  • TEEC_CloseSession:关闭 session,关闭 CA 和 TA 之间的通道;
  • TEEC_FinalizeContext:释放 Context,结束 CA 与 TEE 的连接;

TA 接口:

  • TA_CreateEntryPoint/TA_DestroyEntryPoint:为 CA 建立/移除接入点,注册/取消 TA 的服务;
  • TA_OpenSessionEntryPoint/TA_CloseSessionEntryPoint:建立/关闭 CA 与 TA 之间的通讯通道;
  • TA_InvokeCommandEntryPoint:接收 CA 传送的指令和参数,并在这 TEE 侧执行;

1.5 本部分参考文献

Part 2. TEE 的 OS 移植笔记 (ARM)

2.1 Revisit: ARM TrustZone

2.1.1 Basic Settings

ARM 从 ARMv6 的架构开始引入了 TrustZone 技术。

TrustZone 在硬件层面,借助 Secure Configuration Register(SCR)将 CPU 的工作状态分为了正常世界状态 (Normal World Status,NWS)和安全世界状态 (Secure World Status,SWS)

CPU 在访问安全设备或者安全内存地址空间时,芯片级别的安全扩展组件会去校验 CPU 发送的访问请求的安全状态读写信号位(Non-secure bit,NS bit)是 0 还是 1,以此来判定当前 CPU 发送的资源访问请求是安全请求还是非安全请求。

而处于非安全状态的 CPU 将访问指令发送到系统总线上时,其访问请求的安全状态读写信号位都会被强制设置成 1,表示当前 CPU 的访问请求为非安全请求。

而非安全请求试图去访问安全资源时会被安全扩展组件认为是非法访问的,于是就禁止其访问安全资源,因此该 CPU 访问请求的返回结果要么是访问失败,要么就是返回无效结果,这也就实现了对系统资源硬件级别的安全隔离和保护。

支持 TrustZone 的芯片提供了对外围硬件资源的硬件级别的保护和安全隔离。当 CPU 处于正常世界状态时,任何应用(包括 Rich OS)都无法访问安全硬件设备,也无法访问属于安全世界状态下的内存、缓存(Cache)以及其他外围安全硬件设备。总的来说,TrustZone 需要起到以下作用:

  • 隔离功能(安全状态和非安全状态);
  • 外设和内存 (物理上分开);
  • 总线请求;

这是一个支持 TZ 的 SoC:

TEE 支持基于 TrustZone 提供可信运行环境,为开发人员提供了 API,以方便他们开发实际应用程序。

在实际应用时,可以将用户的敏感数据保存到 TEE 中,并由可信应用(TA)使用重要算法和处理逻辑来完成对数据的处理。当需要使用用户的敏感数据做身份验证时,则通过在 REE 侧定义具体的请求编号(ID)从 TEE 侧获取验证结果。验证的整个过程中用户的敏感数据始终处于 TEE 中,REE 侧无法查看到任何 TEE 中的数据。对于 REE 而言,TEE 中的 TA 相当于一个黑盒,只会接受有限且提前定义好的合法调用(TEEC),而至于这些合法调用到底是什么作用,会使用哪些数据,做哪些操作在 REE 侧是无法知晓的。如果在 REE 侧发送的调用请求是非法请求,TEE 内的 TA 是不会有任何的响应或是仅返回错误代码,并不会暴露任何数据给 REE 侧。

TEE 的系统配置、内部逻辑、安全设备和安全资源的划分是与 CPU 的集成电路设计紧密挂钩的,使用 ARM 架构设计的不同 CPU,TEE 的配置完全不一样。国内外针对不同领域的 CPU 也具有不同的 TEE 解决方案。

这里我想选择开源的 OPTEE 来进行移植,因为它文档支持丰富、提供了完整的 SDK、遵循 GP API。

2.1.2 ARM-v8 的 TrustZone

2.1.3 总线安全扩展

2.1.4 TrustZone 中断控制

2.1.5 MMU 安全扩展

2.1.6 Cache & TLB 安全扩展

TODO

2.2 ARM Trusted Firmware

2.2.1 Concepts

ARM可信任固件(ARM Trusted Firmware,ATF)是由 ARM 官方提供的底层固件,该固件统一 了 ARM 底层接口标准,如电源状态控制接口(Power Status Control Interface,PSCI)、安全启 动需求(Trusted Board Boot Requirements, TBBR)、安全世界状态(SWS)与正常世界状态(NWS)切换的安全监控模式调用(SMC)操作等。ATF 旨在将 ARM 底层的操作统一使代码能够重用和便于移植。

2.2.2 ATF 主要功能

ATF 的源代码共分为 bl1、bl2、bl31、bl32、bl33 部分,其中:

  • bl1、bl2、bl31 部分属于固定的固件;
  • bl32 和 bl33 分别用于加载 TEE OS 和 REE 侧的镜像;整个加载过程可配置成安全启动的方式,每一个镜像文件在被加载之前都会验证镜像文件的电子签名是否合法;
  • bl31 任务是接受 TEE OS 注册的服务入口点,并负责完成安全世界状态和正常世界状态之间的切换;

ATF主要完成的功能如下:

  • 配置和初始化:

    • 初始化 Secure World 状态运行环境、异常向量、 控制寄存器、中断控制器、配置平台的中断。

    • 初始化 ARM 通用中断控制器(General Interrupt Controller,GIC)2.0 版本和 3.0 版本的驱动初始化。

    • 执行 ARM 系统 IP 的标准初始化操作以及安全扩展组件的基本配置。

  • 安全监控模式调用(Secure Monitor Call, SMC)请求的逻辑处理代码(Monitor 模式 / EL3)。

  • 实现可信板级引导功能,对引导过程中加载的镜像文件进行电子签名检查。
  • 支持自有固件的引导,开发者可根据具体需求将自有固件添加到 ATF 的引导流程中。

其中:

  • SMC Dispatcher:处理非安全世界的 SMC 请求,决定哪些 SMC 由 Trusted Firmware 在 EL3 处理,哪些转发给 TEE 进行处理;

  • Trusted Firmware 处理 PSCI 任务、或者 SoC 相关工作,例如一个播放 DRM Video 的调用情况:

    1. 调用相关 CA,使用 libDRM.so
    2. 库调用 TrustZone Driver 向 Secure World 中的 TA 发起请求,同时将相关信息传递到 shared message buffer(非安全)中;
    3. SMC 进入 Secure World 的 TEE OS 后,从 message buffer 中获取相关信息并:
      • Trusting the message:由于 message 是不可信的,所以 secure world 需要对这些内容进行一些认证;
      • Scheduling:对于 PSCI 类型快速处理并且不频繁请求,进入 EL3 处理完后退出到非安全状态。对于一些需要 TEE OS 处理的任务,不能被非安全中断打断,避免造成安全服务不可用;
  • 以 OPTEE 为例,更细致一点:

    • CA 一般不直接使用 TEE Client API,而是使用中间 Service API 提供的服务,TEE Client API 再调用 OP-TEE Driver(暴露在设备文件系统 /dev/tee0),完成后续动作;

    • TEE Supplicant 为 TEE Daemon,有权使用 /dev/teepriv0 设备,在 OP-TEE 驱动的挂载过程中会建立正常世界状态与安全世界状态之间的共享内存,用于 OP-TEE Driver 与 OP-TEE 之间的数据共享;同时还会创建两个链表,分别用于保存来自 OP-TEE 的 RPC 请求和发送 RPC 请求的处理结果给 OP-TEE;

      来自 OP-TEE 的 RPC 请求主要包括 socket 操作、REE 侧文件系统操作、加载 TA 镜像文件、数据库操作、共享内存分配和注册操作等

      • 该进程在 Linux 系统启动过程中被自动创建,在编译时,该进程的启动信息会被写入到 /etc/init.d 文件中,而该进程的可执行文件则被保存在文件系统的 bin 目录下;
      • 该进程中会使用一个 loop 循环接收来自 OP-TEE 的 RPC 请求,且每次获取到来自 OP-TEE 的 RPC 请求后都会自动创建一个线程,用于接收 OP-TEE 驱动队列中来自 OP-TEE 的 RPC 请求信息,之所以这么做是因为时刻需要保证在 REE 侧有一个线程来接收 OP-TEE 的请求,实现 RPC 请求的并发处理;

2.2.3 Targets

现在我们就明白了,如果我们想把 TEE 功能移植到一个 ARM OS 上(例如 ChCore,OpenHarmony EduDist),需要集齐下面的组件:

  • Normal World 状态的客户端库(提供给 CA 使用的 libteec);
  • Normal World 状态的可信驱动(给 Rich OS Kernel 使用的驱动,用以发起 SMC 等操作);
  • Secure World 的可信内核系统及配套的可信硬件驱动(TEE OS & Secure Driver);
  • 安全世界状态的可信应用库(libutee);
  • ATF 固件(应配置启动参数);

2.3 OPTEE + 启动过程试验

2.3.1 基于 QEMU 的 OPTEE 启动过程

观察编译后的二进制目录:

  • Linux (Rich OS) 镜像以及根文件系统:

    • Image、linux.bin、uImage(u-boot wrapper 的 Image);

    • rootfs.cpio.gz;

  • ATF 固件(with OPTEE OS configurations)以及 u-boot;

在 OPTEE 官方提供的启动脚本中,入口 BIOS 为 bl1.bin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
run-only:
ln -sf $(ROOT)/out-br/images/rootfs.cpio.gz $(BINARIES_PATH)/
$(call check-terminal)
$(call run-help)
$(call launch-terminal,54320,"Normal World")
$(call launch-terminal,54321,"Secure World")
$(call wait-for-ports,54320,54321)
cd $(BINARIES_PATH) && $(QEMU_BUILD)/aarch64-softmmu/qemu-system-aarch64 \
-nographic \
-serial tcp:localhost:54320 -serial tcp:localhost:54321 \
-smp $(QEMU_SMP) \
-s -S -machine virt,secure=on,mte=$(QEMU_MTE),gic-version=$(QEMU_GIC_VERSION),virtualization=$(QEMU_VIRT) \
-cpu $(QEMU_CPU) \
-d unimp -semihosting-config enable=on,target=native \
-m $(QEMU_MEM) \
-bios bl1.bin \
-initrd rootfs.cpio.gz \
-kernel Image -no-acpi \
-append 'console=ttyAMA0,38400 keep_bootcon root=/dev/vda2 $(QEMU_KERNEL_BOOTARGS)' \
$(QEMU_XEN) \
$(QEMU_EXTRA_ARGS)

先观察启动日志:

ATF 作为最底层固件,OP-TEE OS、 BootLoader、Linux 内核的加载都是由 ATF 来完成的,而且 ATF 实现了安全引导的功能

bl2 启动时通过触发 SMC 通知 bl1 将 CPU 控制权限交给 bl31,bl31 通过解析特定段中是否存在 OP-TEE Kernel 的入口来确定是否需要加载 OP-TEE。OP-TEE Kernel 启动后会触发安全监控模式调用重新进入到 bl31 中继续执行。

bl31 通过查询链表的方式获取下一个需要被加载 REE 侧的镜像文件,并设定好 REE 侧运行时 CPU 的状态和运行环境,然后退出 EL3 并进入 REE 侧镜像文件的启动,一般第一个 REE 侧镜像文件为 BootLoader,BootLoader 会加载 Linux 内核。

上面的安全引导过程是一个比较复杂和标准化的过程:

2.3.2 设备树文件(Device Tree)

Linux kernel 在 ARM 架构中引入 device tree(flattened device tree,FDT)的时候,怀揣着一个 Unify Kernel 的梦想,即同一个 Image,通过切换不同的 DTB(device tree binary/blob)支持多个不同的平台。

device tree 在 kernel 中普及之后,U-Boot 也引入了 device tree 的概念。因此为了和 kernel 类似,U-Boot也需要一种新的 image 格式,这种格式需要支持以下特性:

  • Image 中需要包含多个 DTB 文件;
  • 可以方便的选择使用哪个 DTB 文件;

结合以上两点需求,U-Boot 推出了新的 image 格式:FIT image(flattened image tree)。它利用了 Device Tree Source files(DTS)的语法,生成的 Image 文件也和 DTB 文件类似(从 ITS 编译为 ITB);

在 ARM64 架构下,U-Boot 启动 Linux 内核必须使用设备树(Device Tree)文件

与早期的 ARM32 架构不同,ARM64 Linux 内核完全移除了对“板级文件”(Board-Specific Files)的支持,强制要求通过设备树(*.dtb)描述硬件配置。没有设备树,内核无法获取硬件信息(如 CPU、内存、外设等),导致启动失败。

U-Boot启动内核时需完成以下步骤:

  1. 加载内核镜像:将 Imagevmlinux 加载到内存。
  2. 加载设备树文件:将编译后的设备树二进制文件(*.dtb)加载到另一块内存区域。
  3. 传递参数:通过寄存器(如 ARM64 的x0)将设备树地址告知内核。

可以在 U-Boot 命令行中执行下面的指令来查看(如果是以 -sd 形式给出):

1
2
3
4
5
# 查看设备树加载地址
printenv fdtaddr
# 手动加载设备树示例
# sd/emmc id=0, partition 1
load mmc 0:1 ${fdtaddr} /boot/myboard.dtb

2.3.2 U-Boot + OpenHarmony Edu Dist 启动方案

考虑最简单的情形,直接由 U-Boot 启动 OpenHarmony,如果成功后再尝试加入 OPTEE。

U-Boot 指令 (UBOOT_BOOTCOMMAND)

booti

1
booti <kernel_addr> [initrd_addr[:initrd_size]] [fdt_addr]
  • booti 命令用于引导内核时加载一个二进制的内核镜像 (Image),通常针对 ARM64 架构。
  • bootm / bootz 类似,booti 也用于启动内核,但是二者针对的镜像格式不同。
    • bootm 主要用于启动 uImage 格式的内核(一般由 mkimage 工具打包而成);
    • bootz 用于启动 zImage 格式的内核;
    • booti 则用于加载 Linux 的原始内核镜像(Image 文件);

尝试一:将 Imageramdisk.img 打包成 boot.img (FAILED)

理想情况下,启动过程应该是:

  • bootloader 初始化 ROM 和 RAM 等硬件,加载分区表信息。
  • bootloader 根据分区表加载 boot.img,从中解析并加载 ramdisk.img 到内存中。
  • bootloader 准备好分区表信息,ramdisk 地址等信息,进入内核,内核加载 ramdisk 并执行 init。
  • init 准备初始文件系统,挂载 required.fstab(包括 system.imgvendor.img 的挂载)。
  • 扫描 system.imgvendor.imgetc/init 目录下的启动配置脚本,执行各个启动命令。

先 dump 出 -machine virt 机器的 DTB 文件:

1
-machine virt,dumpdtb=./virt.dtb

使用 u-boot 编译后的工具 dtc 解析内容(便于查看):

1
dtc -I dtb -O dts ./virt.dtb > virt.dts

然后自行为 U-Boot 编写 ITS(Image Tree Source)文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/dts-v1/;

/ {
description = "U-Boot zImage-dtb-ramdisk";
#address-cells = <1>;
images {
kernel-1 {
description = "Linux kernel ";
data = /incbin/("./Image");
type = "kernel";
arch = "aarch64";
os = "linux";
compression = "none";
load = <0x40800000>;
entry = <0x40800000>;
};
dtb-1 {
description = "ohos dtb ";
data = /incbin/("./virt.dtb");
type = "flat_dt";
arch = "aarch64";
os = "linux";
compression = "none";
};
ramdisk-1 {
description = "Ramdisk Image";
data = /incbin/("./ramdisk.img");
type = "ramdisk";
arch = "aarch64";
os = "linux";
compression = "none";
load = <0x44000000>;
};
};
configurations {
default = "conf-boot";
conf-boot {
description = "booting ARM Linux Kernel Image Ramdisk";
kernel = "kernel-1";
fdt = "dtb-1";
ramdisk = "ramdisk-1";
};
};
};

mkimage 编译生成 boot.img

1
mkimage -f boot.its boot.img

使用 u-boot.bin(U-Boot 编译产物,引导固件)作为 BIOS:

1
-bios u-boot.bin \

然后尝试了两种加载方法:

  • virtio-blk-device 加载;
  • 进入 U-Boot 命令行直接 load 文件:
    • 只显示 virt.dtb 加载到 0x40000000 处;

失败,U-Boot 未能识别分区情况。

尝试二:将 Image, 设备树, ramdisk.img 打包进 SD 设备 (FAILED)

创建空的 SD Card 镜像:

1
dd if=/dev/zero of=boot.disk bs=1M count=1024

创建 GPT 分区,两个分区,一个存放 Kernel 和设备树,另一个存放 Rootfs:

1
2
sgdisk -n 0:0:+10M -c 0:kernel boot.disk
sgdisk -n 0:0:0 -c 0:rootfs boot.disk

检查结果:

1
sgdisk -p boot.disk

找个空闲的 loop 设备,并且把镜像挂载上去,准备写入:

1
2
losetup -f
# 假设返回 /dev/loopxx
1
2
3
sudo losetup /dev/loopxx boot.disk
# 更新之前创建的表,让 host kernel 看到
sudo partprobe /dev/loopxx

对新的分区格式化为 EXT4:

1
2
sudo mkfs.ext4 /dev/loopxxp1
sudo mkfs.ext4 /dev/loopxxp2

挂载到两个任意不同空目录上,然后复制文件:

1
2
3
4
5
sudo mount -t ext4 /dev/loopxxp1 p1/
sudo mount -t ext4 /dev/loopxxp2 p2/
sudo cp Image p1/
sudo cp virt.dtb p1/
sudo cp ramdisk.img p2/

卸载 loop 设备:

1
2
sudo umount p1 p2
sudo losetup -d /dev/loopxx

将写好的 boot.disk 作为 SD 设置 QEMU,U-Boot 启动时用 mmc 指令读取。

失败,U-Boot 未能通过内存校验。

尝试三:使用 mkimage 生成 ITB 文件,U-Boot 直接加载 (BUGGY)

添加 OPTEE 时,注意编译选项给 OPTEE OS 指定的 NS_SHM(Non-Safe Shared Memory)从 0x42000000 开始。因此把 Kernel 地址改到 0x42000000,RamDisk 改到 0x45000000,然后将 mkimage -f xxx.its boot.itb 加载到 0x50200000,并且从该处启动。

最终成功启动 OPTEE Kernel,并且成功加载 OpenHarmony Kernel。但是卡在 Starting Kernel 这步,仍然在排查原因。

2.4 OpenTrustee (ChCore OS) 迁移试验

未完待续…