赞
踩
ARMV8需要掌握4个特权等级(EL0-EL3)、4个安全状态(secure non-secure root realm)、2个执行状态(arch32/arch64)。思考问题:
ARM架构的CPU不断地在各个方向对x86体系发动一波又一波的攻击,甚至在传统x86的强势领域——服务器端,性能差距也在不断接近。作为行业风向标的云服务(CSP)厂商,它们的ARM服务器装机量持续攀升。如亚马逊自研的基于ARM Neoverse的Gravition2 CPU,在AWS EC2的装机量已经几乎和x86持平:
来源:ARM官网
国际CSP全面拥抱ARM服务器的主要原因是技术和成本可控,而性能差距可以接受。出于同样的原因,国内以鲲鹏和飞腾为代表的ARM国产化势力也在持续发力,未来从技术上看希望很大(姑且不论生产卡脖子的问题)。在消费客户端,ARM的成熟度更高。
ARM如此被看好,百敖BIOS自然会全面支持。目前为止,我们在ARM端除了支持国内的鲲鹏和飞腾,也支持国外的NXP。在项目运行中,我发现ARM体系中一些基本概念普及度很低,如ATF/TF-A、Power State Coordination Interface (PSCI)、SMC、Server_Base_Boot_Requirements(SBBR)、Server_Base_System_Architecture(SBSA)、Management Mode(MM)、OPTEE等;还有很多混淆,如ATF和TZ(TrustZone)。我计划写一些文字,分别介绍一下它们,尤其是和固件相关的技术。于此同时,我觉得学习最好能融会贯通,我们将会把这些ARM的概念和x86相应的技术进行对比,希望这样更能够强化大家的理解和记忆。今天就带来第一篇:ATF(ARM Trusted Firmware)。
TF(Trusted Firmware)是ARM在Armv8引入的安全解决方案,为安全提供了整体解决方案。它包括启动和运行过程中的特权级划分,对Armv7中的TrustZone(TZ)进行了提高,补充了启动过程信任链的传导,细化了运行过程的特权级区间。TF实际有两种Profile,对ARM Profile A的CPU应用TF-A,对ARM Profile M的CPU应用TF-M。我们一般接触的都是TF-A,又因为这个概念是ARM提出的,有时候也缩写做ATF(ARM Trusted Firmware),所以本文对ATF和TF-A不再做特殊说明,ATF也是TF-A,对TF-M感兴趣的读者可以自行查询官网[1]。
有些同学混淆了ATF和TZ的区别。实际上,TZ更多的是和Intel的SGX概念对应,是在CPU和内存中区隔出两个空间:Secure空间和Non-Secure空间。而ATF中有个Firmware概念,它实际上是Intel的Boot Guard、特权级和提高版的TZ的混合体。它在保有TZ的Secure空间和Non-Secure空间的同时,划分了EL0(Exception level 0)到EL3四个特权级:
其中EL0和EL1是ATF必须实现的,EL2和EL3是可选的。实际上,没有EL2和EL3,整个模型就基本退化成了ARMv7的TZ版本。从高EL转低EL通过ERET指令,从低EL转高EL通过exception,从而严格区分不同的特权级。其中EL0、EL1、EL2可以分成NS-ELx(None Secure ELx)和S-ELx(Secure ELx)两种,而EL3只有安全模式一种。
ARMv8 没有 Privilege level 的概念,取而代之的是 Exception level(异常级别),简称为EL,用于描述特权级别,一共有 4 个级别:EL0、EL1、EL2 和 EL3,数字越大,级别越高,权限越大!这四个 EL 级别对应的应用场合如下:
ARMv8 提供了两种安全状态:Secure 和 Non-secure,也就是安全和非安全,Non-secure 也就是正常世界(NormalWorld)。我们可以在 Non-secure 运行通用操作系统,比如 Linux,在 Secure 运行可信操作系统,比如OP-TEE,这两个操作系统可以同时运行,这个需要处理器支持 ARM 的 TrustZone 功能。在 Normal world 和 Secure world下,ARMv8 个 EL 等级对应的内容和在 ARMv8 的 AArch32 模式下,处理器模式如图所示:
在 AArch32 模式下,EL0~LE3 对应 ARMv7 的不同工作模式:
可以看出,只有 EL3 是用于安全监视器的,所以 TF-A 主要工作在 EL3 下,在看 TF-A源码的时候会看到大量的“EL3”字样的文件或代码。
ATF带来最大的变化是信任链的建立(Trust Chain),整个启动过程包括从EL3到EL0的信任关系的打通,过程比较抽象。NXP的相关文档[2]比较充分和公开,它的源代码也是开源的[3]。我们结合它的文档和源代码来理解一下。
ARM开源了ATF的基本功能模块,大家可以在这里下载:
git clone https://github.com/ARM-software/arm-trusted-firmware.git
里面已经包含了不少平台,但这些平台的基础代码有些是缺失的,尤其是和芯片部分和与UEFI联动部分。这里我推荐它的一个分支:NXP的2160A芯片的实现。
ARM推出了System Ready计划,效果相当不错,关于它我们今后再单独讲。2020年底,ARM在OSFC推出新的一批System Ready机型[4],NXP 2160A名列其中:
来源:参考资料4
ATF代码下载可以用:
git clone https://source.codeaurora.org/external/qoriq/qoriq-components/atf -b LX2160_UEFI_ACPI_EAR3
UEFI代码下载可以用图片上的地址。我们可以把参考资料2和这些代码对照来看,加深理解。
支持ATF的ARM机器,启动过程如下
来源:参考资料2
注意蓝色箭头上的数字,它是启动顺序。一切起源于在EL3的BL1。
BL1:Trusted Boot ROM
启动最早的ROM,它可以类比Boot Guard的ACM,
老狼:什么是Boot Guard?电脑启动中的信任链条解析269 赞同 · 44 评论文章编辑
不过它是在CPU的ROM里而不是和BIOS在一起,是一切的信任根。它的代码在这里:
代码很简单(略去不重要内容):
- func bl1_entrypoint
- ....
- bl bl1_early_platform_setup
- bl bl1_plat_arch_setup
- ....
- bl bl1_main
- ....
- b el3_exit
- endfunc bl1_entrypoint
bl1_main()开始就是c程序了,那c运行依靠的堆和栈空间在哪里呢?在CPU内部的SRAM里。SRAM一启动就已经可以访问了,bl1_plat_arch_setup()简单地在其中划分出来一块作为Trusted SRAM给c程序用,而不用像x86在cache里面扣一块出来,简单了很多。
BL1主要目的是建立Trusted SRAM、exception vector、初始化串口console等等。然后找到并验证BL2(验签CSF头),然后跳过去。
BL2:Trusted Boot Firmware
同样运行在EL3上的BL2和BL1一个显著的不同是它在Flash上,作为外置的一个Firmware,它的可信建立在BL1对它的验证上。它也有完整的源代码:
它也会初始化一些关键安全硬件和软件框架。更主要的是,也是我希望大家下载NXP 2160A的分支的重要原因,BL2会初始化很多硬件,而这些硬件初始化在x86中是BIOS完成的(无论是在PEI中还是包在FSP/AGESA中),而在ARM的ATF体系中,很多种CPU是在BL2中完成的。2160A在Plat目录下提供了很多开源的硬件初始化代码,供ATF BL2框架代码调用。比较重要的是bl2_main()
- void bl2_main(void)
- {
- ...
- bl2_arch_setup();
- ...
- /* initialize boot source */
- bl2_plat_preload_setup();
- /* Load the subsequent bootloader images. */
- next_bl_ep_info = bl2_load_images();
- ...
- bl2_run_next_image(next_bl_ep_info);
- }
最重要的两步都在这个函数中完成:初始化硬件和找到BL31。
bl2_plat_preload_setup()中会初始化一堆硬件,包括读取RCW初始化Serdes等,对内存初始化感兴趣的人(比如我)也可以在里面找到初始化DDR4的代码:dram_init(),它在Plat\nxp\drivers\ddr\nxp-ddr下。比较遗憾的是DDR4 PHY的代码是个Binary,不含源码,这里对DDR4的初始化仅仅聚焦设置timing寄存器和功能寄存器,而没有内存的Training过程。
Anyway,x86带内初始化硬件的很多代码ARM ATF体系都包括在BL2中,而不在UEFI代码中,这是和x86 UEFI代码的一个显著区别。部分原因这些代码都要求是Secure的。更加糟糕的是,很多ARM平台,BL1和BL2,甚至后面的BL31都是以二进制的形式提供,让定制显得很困难。BL2能否提供足够的信息和定制化选择给固件厂商和提供足够信息给UEFI代码,考验BL2的具体设计实现。NXP在两个方面都做的不错,不但提供RCW等配置接口,还开源了大部分代码,十分方便。
BL2在初始化硬件后,开始寻找BL3的几个小兄弟:BL31,BL32和BL33。它先找到BL31,并验签它,最后转入BL31。
BL31:EL3 Runtime Firmware
BL31作为EL3最后的安全堡垒,它不像BL1和BL2是一次性运行的。如它的runtime名字暗示的那样,它通过SMC为Non-Secure持续提供设计安全的服务。关于SMC的调用calling convention我们今后再详细介绍,这里只需要知道它的服务主要是通过BL32。它负责找到BL32,验签,并运行BL32。
BL32:OPTee OS + 安全app
BL32实际上是著名的Open Portable Trusted Execution Enveiroment[5] OS,它是由Linaro创立的。它是个很大的话题,我们今后再细聊。现在仅需要知道OPTee OS运行在 S-EL1,而其上的安全APP运行在S-EL0。OPTee OS运行完毕后,返回EL3的BL31,BL31找到BL33,验签它并运行。
BL33: Non-Trusted Firmware
BL33实际上就是UEFI firmware或者uboot,也有实现在这里直接放上Linux Kernel。2160A的实现是UEFI和uboot都支持。我们仅仅来看UEFI的路径。
第一次看到UEFI居然是Non-Trusted,我是有点伤心的。UEFI运行在NS_EL2,程序的入口点在ARM package
edk2/ArmPlatformPkg/PrePi/AArch64/ModuleEntryPoint.S
做了一些简单初始化,就跳到C语言的入口点CEntryPoint( )。其中ArmPlatformInitialize()做了一些硬件初始化,调用了
edk2-platforms/Silicon/NXP/ |
的代码。重要的是PrimaryMain()。
PrimaryMain()有两个实例,2160A NXP选择的是PrePI的版本(edk2/ArmPlatformPkg/PrePi/MainUniCore.c),说明它跳过了SEC的部分,直接进入了PEI的后期阶段,在BL2已经干好了大部分硬件初始化的情况下,这个也是正常选择。PrePI的实例直接调用PrePiMain()(仅保留重要部分)
- VOID
- PrePiMain (
- IN UINTN UefiMemoryBase,
- IN UINTN StacksBase,
- IN UINT64 StartTimeStamp
- )
- {
- ....
- ArchInitialize ();
- SerialPortInitialize ();
- InitializeDebugAgent (DEBUG_AGENT_INIT_POSTMEM_SEC, NULL, NULL);
- // Initialize MMU and Memory HOBs (Resource Descriptor HOBs)
- Status = MemoryPeim (UefiMemoryBase, FixedPcdGet32 (PcdSystemMemoryUefiRegionSize));
- BuildCpuHob (ArmGetPhysicalAddressBits (), PcdGet8 (PcdPrePiCpuIoSize));
- BuildGuidDataHob (&gEfiFirmwarePerformanceGuid, &Performance, sizeof (Performance));
- SetBootMode (ArmPlatformGetBootMode ());
- // Initialize Platform HOBs (CpuHob and FvHob)
- Status = PlatformPeim ();
- ....
- Status = DecompressFirstFv ();
- Status = LoadDxeCoreFromFv (NULL, 0);
- }
从中我们可以看到,这里几乎就是UEFI PEI阶段DXEIPL的阶段了,后面就是直接DXE阶段。
好了,我们来梳理一下,ATF整个信任链条是逐步建立的:
来源:参考资料2
从作为信任根的BL1开始,一步一步验签CSF头中的签名,最后来到BL33,后面就是OS了。那BL33后面怎么就断了呢?其实后面的验签就是UEFI Secure Boot了
老狼:趣话安全启动:迷思与启示152 赞同 · 24 评论文章编辑
ATF的官网一张图包含了更多的信息:
如果你仅仅对ATF的UEFI启动路径感兴趣,下面这张图可能更加简单明了:
NXP 2160A的开源和良好的文档,让我们可以在一个具体的平台上切片观察ATF的具体实现,建议大家仔细阅读参考资料2和下载代码来看看。
关于ATF启动这里先整个宏观的概念。
这个blog讲的很好,就不重复写了,自己写还写不到这么清晰,图页很漂亮。
原文链接:https://www.cnblogs.com/arnoldlu/p/14175126.html
下图划分成不同EL,分别描述BL1、BL2、BL31、BL32、BL33启动流程,以及PSCI、SP处理流程。
restart–冷启动
reset–热启动
ATF冷启动实现分为5个步骤:
ATF输出BL1、BL2、BL31,提供BL32和BL33接口。
(我想提供的接口就是BL32和BL33的镜像可以是指定的,atf其实是一个启动框架,这其中包含的五个步骤,每个步骤你想要的内容,可以由厂商自己定义。)
启动流程如下:
BL1位于ROM中,在EL3下从reset vector处开始运行。(bootrom就是芯片上电运行的(chip-rom的作用就是跳转到bootrom))
BL1做的工作主要有:
BL2位于SRAM中,运行在Secure EL1主要工作有:
BL31位于SRAM中,EL3模式。除了做架构初始化和平台初始化外,还做了如下工作:
这两幅图真的不错,棒。
ATF将系统启动从最底层进行了完整的统一划分,将secure monitor的功能放到了bl31中进行,这样当系统完全启动之后,在CA或者TEE OS中触发了smc或者是其他的中断之后,首先是遍历注册到bl31中的对应的service来判定具体的handle,这样可以对系统所有的关键smc或者是中断操作做统一的管理和分配。
在上述启动过程中,每个Image跳转到写一个image的方式各不相同,下面将列出启动过程中每个image跳转到下一个image的过程:
在bl1完成了bl2 image加载到RAM中的操作,中断向量表设定以及其他CPU相关设定之后,在bl1_main函数中解析出bl2 image的描述信息,获取入口地址,并设定下一个阶段的cpu上下文,完成之后,调用el3_exit函数实现bl1到bl2的跳转操作,进入到bl2中执行.
在bl2中将会加载bl31, bl32, bl33的image到对应权限的RAM中,并将该三个image的描述信息组成一个链表保存起来,以备bl31启动bl32和bl33使用在AACH64中,bl31位于EL3 runtime software,运行时的主要功能是管理smc指令的处理和中断的主力,运行在secure monitor状态中
bl32一般为TEE OS image,本章节以OP-TEE为例进行说明
bl33为非安全image,例如uboot, linux kernel等,当前该部分为bootloader部分的image,再由bootloader来启动linux kernel.(所以不会这么久就是为了整个bootloader吧,应该是kernel吧)
从bl2跳转到bl31是通过带入bl31的entry point info调用smc指令触发在bl1中设定的smc异常来通过cpu将全向交给bl31并跳转到bl31中执行。(这个handle是再bl1配置的)
在bl31中会执行runtime_service_inti操作,该函数会调用注册到EL3中所有service的init函数, 其中有一个service就是为TEE服务,该service的init函数会将TEE OS的初始化函数赋值给bl32_init变量,当所有的service执行完init后,在bl31中会调用bl32_init执行的函数来跳转到TEE OS的执行
当TEE_OS image启动完成之后会触发一个ID为TEESMC_OPTEED_RETURN_ENTRY_DONE的smc调用来告知EL3 TEE OS image已经完成了初始化,然后将CPU的状态恢复到bl31_init的位置继续执行。
bl31通过遍历在bl2中记录的image链表来找到需要执行的bl33的image。然后通过获取到bl33 image的镜像信息,设定下一个阶段的CPU上下文,退出el3然后进入到bl33 image的执行
这一步对宏观的步骤有所认识,下一步对每个步骤的细节进行认识。
在上述启动过程中,每个Image跳转到写一个image的方式各不相同,下面将列出启动过程中每个image跳转到下一个image的过程:
1. bl1跳转到bl2执行
在bl1完成了bl2 image加载到RAM中的操作,中断向量表设定以及其他CPU相关设定之后,在bl1_main函数中解析出bl2 image的描述信息,获取入口地址,并设定下一个阶段的cpu上下文,完成之后,调用el3_exit函数实现bl1到bl2的跳转操作,进入到bl2中执行.
2.bl2跳转到bl31执行
在bl2中将会加载bl31, bl32, bl33的image到对应权限的RAM中,并将该三个image的描述信息组成一个链表保存起来,以备bl31启动bl32和bl33使用.在AACH64中,bl31位EL3 runtime software,运行时的主要功能是管理smc指令的处理和中断的主力,运行在secure monitor状态中
bl32一般为TEE OS image,本章节以OP-TEE为例进行说明
bl33为非安全image,例如uboot, linux kernel等,当前该部分为bootloader部分的image,再由bootloader来启动linux kernel.
从bl2跳转到bl31是通过带入bl31的entry point info调用smc指令触发在bl1中设定的smc异常来通过cpu将全向交给bl31并跳转到bl31中执行。
3.bl31跳转到bl32执行
在bl31中会执行runtime_service_inti操作,该函数会调用注册到EL3中所有service的init函数,其中有一个service就是为TEE服务,该service的init函数会将TEE OS的初始化函数赋值给bl32_init变量,当所有的service执行完init后,在bl31中会调用bl32_init执行的函数来跳转到TEE OS的执行
4.bl31跳转到bl33执行
当TEE_OS image启动完成之后会触发一个ID为TEESMC_OPTEED_RETURN_ENTRY_DONE的smc调用来告知EL3 TEE OS image已经完成了初始化,然后将CPU的状态恢复到bl31_init的位置继续执行。
bl31通过遍历在bl2中记录的image链表来找到需要执行的bl33的image。然后通过获取到bl33 image的镜像信息,设定下一个阶段的CPU上下文,退出el3然后进入到bl33 image的执行
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。