TEE OS(BL32)的启动过程 Hi!不知不觉就到了周四,这周有没有感觉自己又厉害了一点?没有的话,快好好把本文看完哈哈哈【狗头】
如果对你有帮助的欢迎点赞关注分享再看,如果你想给我打赏,请拿去给你自己买杯水,当我请你的哈哈哈,话不多说,开始今天的安全启动之旅!!!
需要转载的请私信免费加全局白名单。
一、BL32(TEE OS)的准备 ATF启动流程 ATF流程 ATF冷启动实现分为5个步骤:
· BL1 - AP Trusted ROM,一般为BootRom。
· BL2 - Trusted Boot Firmware,一般为Trusted Bootloader。
· BL31 - EL3 Runtime Firmware,一般为SML,管理SMC执行处理和中断,运行在secure monitor中。
· BL32 - Secure-EL1 Payload,一般为TEE OS Image。
· BL33 - Non-Trusted Firmware,一般为uboot、linux kernel。
BL32的加载 BL2位于SRAM中,运行在Secure EL1主要工作有:
· 架构初始化:EL1/EL0使能浮点单元和ASMID。
· 平台初始化:控制台初始化、相关存储设备初始化、MMU、相关设备安全配置、
· SCP_BL2:系统控制核镜像加载,单独核处理系统功耗、时钟、复位等控制。
· 加载BL31镜像:BL2将控制权交给BL1;BL1关闭MMU并关cache;BL1将控制权交给BL31。
· 加载BL32镜像:BL32运行在安全世界,BL2依赖BL31将控制权交给BL32。SPSR通过Secure-EL1 Payload Dispatcher进行初始化。
· 加载BL33镜像:BL2依赖BL31将控制权交给BL33。
二、OP-TEE 的启动过程 OP-T EE 是一个 开源工程,完整的实现了一个可信执行环境。 该项目最初由意法-爱立信所发起,是一个专门的解决方案,然后由ST半导体拥有和维护,2014年Linaro开始与意法半导体合作推出将这个专有的TEE解决方案转换成一个开源的TEE解决方案。
2015年9月,Linaro拥有了这个项目。今天它成为Linaro的一个关键的安全项目,Linaro的几个成员的支持和使用它。 OP-TEE是目前支持最多芯片厂商的开源可信操作系统!安智客将各大芯片厂商进行了分析整理,统计出如下芯片厂商支持OP-TE: T EEOS确实有很多种 ,但是开源的 蛮少的, 这里就拿了 O P- TEE作为示例讲解一下TEEOS的启动过程。
如果系统支持ATF,则OP-TEE OS镜像的加载由ATF来完成,OP-TEE属于ATF中的bl32阶段 ,ATF的bl31阶段调用opteed_entry_sp函数跳转到OP-TEE OS中执行OP-TEE OS的启动。
32位系统的OP-TEE与64位系统的OP-TEE的启动过程只是底层的执行流程不一致,其他过程则大致相同。
本节将介绍ARM32位系统的启动过程以及ARM64位系统OP-TEE的启动过程与ARM32位OP-TEE系统的启动过程的差异。
1 OP-TEE OS的入口函数 · OP-TEE镜像的入口函数是在编译OP-TEE OS时通过链接文件来确定的,
· OP-TEE在编译时是按照optee_os/core/arch/arm/kernel/kern.ld.S文件链接生成OP-TEE OS的镜像文件,
· 在kern.ld.S文件中通过ENTRY宏来指定OP-TEE OS的入口函数,在OP-TEE中指定的入口函数是_start,
2 OP-TEE的内核初始化过程 ** _start会调用reset函数进入OP-TEE OS的启动过程**。
由于对称多处理(Symmetr ical Multi-Processing, SMP)架构的原因,在reset函数中会对主核和从核进行不同的启动操作,分别调用reset_primary函数和reset_secondary函数来完成。
1. reset入口函数执行内容 reset函数是主核和从核启动的第一个函数,该函数的执行流程如图所示。
reset函数执行流程 进入到reset函数后,系统会将_start的地址写入VBAR寄存器作为中断向量表的起始地址使用 ,
在启动从核时,从核知道会到该地址去获取应该执行代码来完成从核的启动 。整个reset函数的内容和注释如下:
LOCAL_FUNC reset , : UNWIND( .fnstart) UNWIND( .cantunwind) bootargs_entry //获取启动带入的参数,主要是启动地址、device tree地址等 /* 使能对齐检查并禁用数据和指令缓存 */ read_sctlr r0 //读取sctlr中的数据,获取当前CPU控制寄存器中的值 #if defined(CFG_SCTLR_ALIGNMENT_CHECK) orr r0, r0, #SCTLR_A //设定对齐校验 #else bic r0, r0, #SCTLR_A #endif bic r0, r0, #SCTLR_C //关闭数据cache bic r0, r0, #SCTLR_I //关闭指令cache #if defined(CFG_HWSUPP_MEM_PERM_WXN) && defined(CFG_CORE_RWDATA_NOEXEC) orr r0, r0, #(SCTLR_WXN | SCTLR_UWXN) #endif write_sctlr r0 //将r0写入到sctlr中,用于关闭cache isb /* 早期ARM核安全监控模式态的特殊配置 */ bl plat_cpu_reset_early //执行CPU早期初始化 ldr r0, =_start //设定r0寄存器的值为_start函数的地址 write_vbar r0 //将_start函数的地址写入VBAR寄存器中,用于启动时使用 #if defined(CFG_WITH_ARM_TRUSTED_FW) b reset_primary //支持ATF时跳转到reset_primary中执行 #else bl get_core_pos //判定当前CPU CORE的编号 cmp r0, #0 //将获得的CPU编号与0对比 beq reset_primary //如果当前core是主核,则使用reset_primary进行初始化 b reset_secondary //如果当前core是从核,则使用reset_secondary进行初始化 #endif UNWIND( .fnend) END_FUNC reset
plat_cpu_reset_early函数将会设定SCR寄存器中的安全标志位 ,用于标记当前CPU是处于安全世界状态中,并且将_start地址写入VBAR寄存器,用于在需要启动从核时系统能找到启动代码的入口地址,
reset_primary函数是主核启动代码的入口函数,该函数将会启动主核的基本初始化、配置运行环境,然后再开始执行唤醒从核的操作。
2. reset_primary函数的执行 本小节以CONFIG_BOOT_SYNC_CPU使能为例, 在使能PSCI系统中,不需要使能此宏 。
reset_primary函数执行流程 reset_primary函数是OP-TEE对CPU主核进行初始化操作的函数,该函数会初始化系统的MMU,并调用generic_boot_init_primary函数完成OP-TEE运行环境的建立,然后触发sev操作来唤醒从核 ,待所有CPU核都启动完成之后 ,OP-TEE会触发安全监控模式调用(smc),通知系统OP-TEE启动已完成并将CPU的状态切换回到正常世界状态 ,该函数的执行流程如图所示。
reset_primary函数的主要代码内容如下:
LOCAL_FUNC reset_primary , : UNWIND( .fnstart) UNWIND( .cantunwind) /* 清空BSS段 */ ldr r0, =__bss_start ldr r1, =__bss_end mov r2, #0 mov r3, #0 clear_bss: stmia r0! , {r2, r3} cmp r0, r1 bls clear_bss /* 初始化内存shadow区域,并设定权限 */ #ifdef CFG_CORE_SANITIZE_KADDRESS ldr r0, =__asan_shadow_start ldr r1, =__asan_shadow_end mov r2, #ASAN_DATA_RED_ZONE shadow_no_access: str r2, [r0], #4 cmp r0, r1 bls shadow_no_access /* 标记整个栈区域准备完成 */ ldr r2, =CFG_ASAN_SHADOW_OFFSET ldr r0, =__nozi_stack_start lsr r0, r0, #ASAN_BLOCK_SHIFT add r0, r0, r2 ldr r1, =__nozi_stack_end lsr r1, r1, #ASAN_BLOCK_SHIFT add r1, r1, r2 mov r2, #0 shadow_stack_access_ok: strb r2, [r0], #1 cmp r0, r1 bls shadow_stack_access_ok #endif set_sp //设定sp寄存器 bl plat_cpu_reset_late //core的后期初始化,可根据具体情况执行特定操作 bl console_init //初始化log数据 inval_cache_vrange(__text_start, __end) //在初始化阶段禁止数据cache bl core_init_mmu_map //初始化MMU页表 bl core_init_mmu_regs //将MMU页表信息写入MMU的TTBRx寄存器中 bl cpu_mmu_enable //使能MMU bl cpu_mmu_enable_icache //使能MMU的指令cache bl cpu_mmu_enable_dcache //使能MMU的数据cache mov r0, r4 /* 页表区域的地址 */ mov r1, r5 /* 非安全入口地址 */ mov r2, r6 /* 设备树地址 */ //带入paged_table、Linux内核的地址、设备树信息进入OP-TEE系统运行环境的建立 bl generic_boot_init_primary mov r4, r0 flush_cache_vrange(__text_start, __end) //刷新cache cpu_is_ready //设定CPU主核已经ready flush_cpu_semaphores //刷新信号量通知从核启动 wait_secondary //等待从核启动完成 bl thread_clr_boot_thread //清空系统各thread的状态 #if defined(CFG_WITH_ARM_TRUSTED_FW) mov r1, r4 //如果支持ATF,则将OP-TEE的handle返回给ATF #else mov r4, #0 mov r3, r6 mov r2, r7 mov r1, #0 #endif /* CFG_WITH_ARM_TRUSTED_FW */ mov r0, #TEESMC_OPTEED_RETURN_ENTRY_DONE //设定返回给Normal World的值 smc #0 //调用SMC操作切回到Normal World状态,OP-TEE启动结束 b . /* SMC should not return */ UNWIND( .fnend) END_FUNC reset_primary
3. generic_boot_init_primary函数内容 generic_boot_init_primary函数是OP-TEE建立系统运行环境的入口函数,该函数会进行建立线程运行空间、初始化OP-TEE内核组件等操作。该函数的执行流程如图所示。
generic_boot_init_primary函数执行流程 generic_boot_init_primary函数会调用init_primary_helper函数来完成系统运行环境的建立 ,如果系统支持ATF,则该函数会返回OP-TEE的处理句柄,该处理句柄主要包含
· 各种安全监控模式调用的处理函数、
· 安全世界状态(SWS)的中断
· 以及其他事件的处理函数,
ATF中的bl31解析完安全监控模式调用或中断请求后会在安全世界状态调用该处理句柄来处理对应的事件。
init_primary_helper函数的主要内容如下:
static void init_primary_helper(unsigned long pageable_part, unsigned long nsec_entry, unsigned long fdt) { thread_set_exceptions(THREAD_EXCP_ALL); //设置支持哪些异常处理 init_vfp_sec(); //初始化浮点运算(根据实际需要考虑是否开启) //初始化各种memory,清空BSS段,分配TA运行时的memory init_runtime(pageable_part); /* 初始化TEE中支持的线程栈、异常处理、pagetable */ thread_init_primary(generic_boot_get_handlers()); //初始化每个CPU的monitor态的处理方式,如果支持ATF,则无需该操作 thread_init_per_cpu(); /* 如果系统不支持ATF,则需要配置在Linux内核中monitor的处理方式 */ init_sec_mon(nsec_entry); /* 初始化device tree */ init_fdt(fdt); /* 初始化中断控制器 */ main_init_gic(); /* 初始化非安全侧的浮点运算 */ init_vfp_nsec(); /* 初始化共享内存并执行存放在__initcall_start段的其他初始化函数 */ if (init_teecore() ! = TEE_SUCCESS) panic(); DMSG('Primary CPU switching to normal world boot\n'); }
init_primary_helper函数最后会调用init_teecore来完成OP-TEE内核的初始化 ,在init_teecore函数中会设定共享内存、系统时间,然后再返回去执行OP-TEE镜像文件中的_initcall段中的内容来启动系统的服务以及安全驱动的挂载。
4. call_initcalls函数 init_teecore函数通过调用call_initcalls来启动系统的服务以及安全驱动的挂载 ,该函数的内容如下:
static void call_initcalls(void) { initcall_t *call; /* 遍历并执行_initcallx段中所有函数 */ for (call = &__initcall_start; call < &__initcall_end; call ) { TEE_Result ret; ret = (*call)(); if (ret ! = TEE_SUCCESS) { EMSG('Initial call 0x%08' PRIxVA ' failed', (vaddr_t)call); } } }
在执行call_initcalls函数之前,系统已完成了memory、CPU相关设置、中断控制器、共享内存、线程堆栈设置、TA运行内存的分配等操作 。
call_initcalls是通过遍历OP-TEE镜像文件的_initcall段中从_initcall_start到_initcall_end之间的所有函数来完成启动服务和驱动的挂载操作。
OP-TEE镜像文件中** _initcalls段的内容是通过使用__define_initcall宏来告知编译器的**,在编译时会将使用该宏定义的函数保存到OP-TEE镜像文件的_initcall段中。该宏定义如下:
#define __define_initcall(level, fn) \ static initcall_t __initcall_##fn __attribute__((used)) \ __attribute__((__section__('.initcall' level))) = fn
例如,如果使用该宏如下:
__define_initcall('1', init_operation)
则该宏的作用是声明一个名称为__initcall_init_operation的函数指针,将该函数指针初始化为init_operation,并在编译时将该函数的内容存放在名称为“.initcall1”的段中。
core/arch/arm/kernel/kern.ld.S文件中存在如下内容:
__initcall_start = .; KEEP(*(.initcall1)) KEEP(*(.initcall2)) KEEP(*(.initcall3)) KEEP(*(.initcall4)) __initcall_end = .;
即在__initcall_start到__initcall_end之间保存的是initcall1到initcall4之间的内容,而在整个OP-TEE源代码的core/include/initcall.h文件中,__define_initcall宏被使用的情况如下:
#define __define_initcall(level, fn) \ #define service_init(fn) __define_initcall('1', fn) #define service_init_late(fn) __define_initcall('2', fn) #define driver_init(fn) __define_initcall('3', fn) #define driver_init_late(fn) __define_initcall('4', fn)
所以遍历执行从__initcall_start到__initcall_end之间的内容就是启动OP-TEE的服务以及完成安全驱动的挂载。
3 OP-TEE服务项的启动 OP-TEE服务项的启动分为:service_init以及service_init_late ,需要被启动的服务项通过使用这两个宏,在编译时,相关服务的内容将会被保存到initcall1和initcall2中。
1. service_init宏 在OP-TEE使用中使用service_init宏定义的服务项如下:
service_init(register_supplicant_user_ta); service_init(verify_pseudo_tas_conformance); service_init(tee_cryp_init); service_init(tee_se_manager_init);
如果开发者有实际需求,可以将自己希望添加的服务项功能按照相同的方式添加到系统中。
在当前的OP-TEE中默认是启动上述四个服务,分别定义在以下文件:
register_supplicant_user_ta: core/arch/arm/kernel/ree_fs_ta.c verify_pseudo_tas_conformance: core/arch/arm/kernel/pseudo_ta tee_cryp_init: core/tee/tee_cryp_utl.c tee_se_manager_init: core/tee/se/manager.c
register_supplicant_user_ta部分: 该操作主要是注册OP-TEE加载REE侧的TA镜像时需要使用的操作接口 ,当REE侧执行open session操作时,TEE侧会根据UUID的值在REE侧的文件系统中查找该文件,然后通过RPC请求通知tee_supplicant从REE的文件系统中读取与UUID对应的TA镜像文件的内容并传递到TEE侧。
verify_pseudo_tas_conformance部分: 该函数主要是用来校验OP-TEE中静态TA的合法性,需要检查OP-TEE OS中静态TA的UUID、函数指针以及相关的flag。该段代码如下:
static TEE_Result verify_pseudo_tas_conformance(void) { //获取存放psedo TAs的head info的段起始地址 const struct pseudo_ta_head *start = &__start_ta_head_section; //获取存放psedo TAs的head info的段末尾地址 const struct pseudo_ta_head *end = &__stop_ta_head_section; const struct pseudo_ta_head *pta; //定义一个指向TA head的变量指针 for (pta = start; pta < end; pta ) { const struct pseudo_ta_head *pta2; /* 检查psedo TAs的head info中包含的UUID信息是否有相同的 */ for (pta2 = pta 1; pta2 < end; pta2 ) if (! memcmp(&pta->uuid, &pta2->uuid, sizeof(TEE_UUID))) goto err; /* 检查invoke函数指针是否为空和相关的flag是否合法 */ if (! pta->name || (pta->flags & PTA_MANDATORY_FLAGS) ! = PTA_MANDATORY_FLAGS || pta->flags & ~PTA_ALLOWED_FLAGS || !pta->invoke_command_entry_point) goto err; } return TEE_SUCCESS; err: DMSG('pseudo TA error at %p', (void *)pta); panic('pta'); }
OP-TEE OS镜像文件中的__start_ta_head_section与__stop_ta_head_section之间保存的是OP-TEE所有静态TA的内容,其值的定义见core/arch/arm/kernel/kern.ld.S文件,分别表示ta_head_section段的起始地址和末端地址。
在编译OP-TEE的静态TA时,使用pseudo_ta_register宏来告知编译器将静态TA的内容保存到ta_head_section段中,该宏定义在core/arch/arm/include/kernel/pseudo_ta.h文件中,内容如下:
#define pseudo_ta_register(...) static const struct pseudo_ta_head __head \ __used __section('ta_head_section') = { __VA_ARGS__ }
共有六个静态TA在OP-TEE编译时会被打包进OP-TEE的镜像文件中,分别如下:
gprof: core/arch/arm/pta/gprof.c interrupt_tests.ta: core/arch/arm/pta/Iiterrupt_tests.c stats.ta: core/arch/arm/pta/stats.c se_api_self_tests.ta: core/arch/arm/pta/se_api_self_tests.c socket: core/arch/arm/tee/pta_socket.c invoke_tests.pta: core/arch/arm/pta/pta_invoke_test.c
tee_cryp_init部分: 该部分主要完成OP-TEE提供的密码学接口功能的初始化操作,调用crypto_ops结构体中的init进行初始化操作,该结构体变量定义在core/lib/libtomcrypt/src/tee_ltc_provider.c文件中,变量中定义了各种算法的操作函数指针。
完成注册后,TA就可以通过调用该变量中的对应函数指针来实现OP-TEE中各种密码学算法接口的调用。
tee_se_manager_init部分: 该部分主要完成对SE模块的管理,为上层提供对SE模块的操作接口。
2. service_init_late宏 service_init_late宏定义的内容将会在编译时被链接到OP-TEE镜像文件的initcall2段中 ,OP-TEE中使用该宏来定义OP-TEE中使用的密钥管理操作 ,在core/tee/tee_fs_key_manager.c文件中,使用该宏来将tee_fs_key_manager函数保存到initcall2段中,
在OP-TEE启动时被调用,用来生成或读取OP-TEE在使用时会使用到的key,该函数内容如下:
static TEE_Result tee_fs_init_key_manager(void) { int res = TEE_SUCCESS; struct tee_hw_unique_key huk; uint8_t chip_id[TEE_FS_KM_CHIP_ID_LENGTH]; uint8_t message[sizeof(chip_id) sizeof(string_for_ssk_gen)]; /* 获取机器唯一的key作为salt值 */ tee_otp_get_hw_unique_key(&huk); /* 获取chip ID值 */ tee_otp_get_die_id(chip_id, sizeof(chip_id)); /* 将unique key和chip id存放到message变量中 */ memcpy(message, chip_id, sizeof(chip_id)); memcpy(message sizeof(chip_id), string_for_ssk_gen, sizeof(string_for_ssk_gen)); /* 调用HMAC算法,以获取到的message作为参数传入来计算出一串字符串作为key存放到tee_ fs_ssk变量中的key成员中 */ res = do_hmac(tee_fs_ssk, key, sizeof(tee_fs_ssk.key), huk.data, sizeof(huk.data), message, sizeof(message)); if (res == TEE_SUCCESS) tee_fs_ssk.is_init = 1; return res; }
这些key将会在使用安全存储功能时用到,用于生成加密、解密安全文件的FEK,其中tee_otp_get_hw_unique_key函数可根据不同的平台进行修改 ,只要保证读取到的值的唯一性且安全即可,当前一般做法是读取一次性编程区域(One Time Programmable, OTP)或efuse中的值,该值将在芯片生产或者工厂整机生产时烧录到OTP中,当然也有其他的实现方式。
4 OP-TEE驱动的挂载 安全设备在使用之前都需要执行一定的配置和初始化,而该部分操作是在OP-TEE启动时执行的。
OP-TEE编译时通过使用driver_init宏和driver_init_late宏来实现将安全设备驱动编译到OP-TEE OS镜像文件中,使用这两个宏定义设备驱动后,安全设备驱动的初始化操作将会被编译到OP-TEE镜像文件的initcall3和initcall4段中。
以Hikey为例,其使用了driver_init宏来定义peripherals_init的初始化操作,所以在使用hikey运行OP-TEE时会去挂载外围安全设备并执行相关的初始化。
三、ARM64位与ARM32位OP-TEE启动过程的差异 ARM32的OP-TEE与ARM64的OP-TEE启动过程大致相同。ARM64的OP-TEE的_start函数定义在generic_entry_a64.S文件中,而且该函数不像ARM32位系统一样会进入reset中去执行OP-TEE启动,而是直接在_start函数中就完成整个启动过程,
在进行初始化操作之前会注册一个异常向量表,该异常向量表会在唤醒从核阶段被使用,当主核通知唤醒从核时,从核会查找该异常向量表,然后命中对应的处理函数并执行从核的启动操作。
ARM64的OP-TEE的启动过程与ARM32的OP-TEE的启动过程几乎一样。ARM64位系统的_start函数内容说明如下:
FUNC _start , : mov x19, x0 //保存paged_table的地址到x19中 mov x20, x2 //保存device tree的地址到x20中 adr x0, reset_vect_table //获取异常向量表的地址 msr vbar_el1, x0 //将异常向量表的地址写入VBAR寄存器中 isb //设置系统控制寄存器,禁止cache等操作 mrs x0, sctlr_el1 mov x1, #(SCTLR_I | SCTLR_A | SCTLR_SA) orr x0, x0, x1 msr sctlr_el1, x0 isb //复制OP-TEE镜像中的init部分到内存中 copy_init: ldp x3, x4, [x1], #16 stp x3, x4, [x0], #16 cmp x0, x2 b.lt copy_init msr daifclr, #DAIFBIT_ABT //使能异常处理 adr x0, __text_start //将__text_start的地址保存到x0中 adrp x1, __end //将_end的地址保存到x1中 add x1, x1, :lo12:__end sub x1, x1, x0 bl inv_dcache_range //关闭数据cache bl console_init //初始化console bl core_init_mmu_map //初始化MMU的页表 bl core_init_mmu_regs //将MMU的页表信息写入TTBRx寄存器中 bl cpu_mmu_enable //使能MMU bl cpu_mmu_enable_icache //使能MMU的指令cache bl cpu_mmu_enable_dcache //使能MMU的数据cache mov x0, x19 //将paged_table的地址保存到x0中 mov x1, #-1 mov x2, x20 //将device tree的地址保存到x2中 //使用device tree和paged_table作为参数开始OP-TEE的启动 bl generic_boot_init_primary mov x19, x0 adr x0, __text_start add x1, x1, :lo12:__end sub x1, x1, x0 bl flush_dcache_range //刷新数据cache bl thread_clr_boot_thread //清空系统线程的状态 mov x1, x19 //将TEESMC_OPTEED_RETURN_ENTRY_DONE保存到x0 mov x0, #TEESMC_OPTEED_RETURN_ENTRY_DONE smc #0 //调用SMC切换到normal world状态 b . /* SMC不应该有返回操作 */ END_FUNC _start
四、小结 本章介绍了OP-TEE的整个启动过程,包括ARM32的OP-TEE与ARM64的OP-TEE启动过程之间的差异。
了解系统的启动过程,有助于理解OP-TEE如何保证上层软件的安全,如何在系统级别添加新的功能,以及安全驱动的挂载方式。
参考资料 · 1、https://blog.csdn.net/weixin_45264425/article/details/126634277
· 2、https://blog.csdn.net/weixin_45264425/article/details/126634478
· 3、https://blog.csdn.net/weixin_45264425/article/details/126634498
· 4、https://blog.csdn.net/weixin_45264425/article/details/132266019?spm=1001.2014.3001.5502
· 5、《可信应用开发指南》
· 6、https://www.cnblogs.com/arnoldlu/p/14332530.html
· 7、https://github.com/ARM-software/arm-trusted-firmware
· 8、https://github.com/Xilinx/arm-trusted-firmware
· 9、https://trustedfirmware-a./en/latest/
· 10、 https://cloud.tencent.com/developer/article/1079822?from=15425