ATF 可信启动调研
# ARM 安全技术(ATF 的可信启动)
# 一、目标
本文主要针对 ATF 的可信启动进行分析,可信机制主要位于代码的 BL2 文件夹中,目录结构如下:
为便于后续分析给出 ATF 加载流程,给出官网中一个直观的流程图:
另外 ATF 固件源码整体架构如下:
# 二、核心服务原理分析
# (一)安全启动与信任链
安全启动是建立系统信任链(Chain of Trust)的基础。信任链(Chain of Trust)是基于根信任(Root of trust)创建的, 而根信任的实现是基于两种技术:不可修改的 bootloader 和不可被修改的公钥。公钥通常存放在 OTP(One-Time-Programmable)内存中, bootloader 同常存储在 ROM 中或者不可修改的 Flash 内存中。
# (二)安全启动流程图
为了便于后续对代码的理解分析,这里先给出安全启动流程图
# 说明:
# arch 初始化
对于 AArch64:
BL2 执行 normal world 和后续阶段所需的最小架构初始化。
通过清零 CPACR.FPEN 位,使 EL1 和 EL0 可以访问 SIMD 寄存器。
BL2 主要执行如下初始化步骤:
1、初始化 console (PL101).(尽管在 bl1 时已经初始化过一次)
2、初始化和配置储存设备驱动,用于加载后续的 bl。
3、使能 MMU ,map the memory,访问权限.
4、平台安全设置,相关组件(寄存器,外设,地址等)的访问控制
5、为 BL3 阶段的 image 保留内存空间。
6、为 BL3 阶段的 image 定义可用内存地址范围。
7、如果 BL1 使用 TB_FW_CONFIG dynamic configuration file (保存在 arg0) , 解析配置参数
# image load
BL2 通过查找 image list 的方式加载 image,并且将这个 list 传递给下一个 BL 镜像。
平台实现方法提供的可加载 image list 还可以包含动态配置文件。这个配置文件可以根据需要在 bl2_plat_handle_post_image_load()函数中进行解析。 通过更新此函数中的相应 ep 信息,可以将这些配置文件作为参数传递给下一个 Boot Loader 阶段。
# SCP_BL2 image load
BL2 将可选的 SCP_BL2 镜像从平台存储设备加载到特定的安全内存区域。 SCP_BL2 的后续处理是特定于具体平台的,需要自行实现。 例如,Arm Juno ,BL2 先把 SCP_BL2 加载到 trust sram,再使用 Boot Over MHU (BOM) 协议,把 SCP_BL2 加载到 SCP 的内部 RAM 之后,SCP 运行 SCP_BL2,并给 AP 发出 signals,通知 BL2 继续执行。
# Load EL3 software
BL2 从平台存储设备加载 EL3 runtime software 到 trusted SRAM. 如果内存空间不够或者镜像不存在去,则 assert 停止运行。
# AArch64(Secure-EL1 payload) image load
BL2 将可选的 BL32 镜像从平台存储设备加载到特定于平台的安全存储区域。BL32 镜像在安全世界中执行。BL2 依靠 BL31 将控制权限传递给 BL32(如果存在)。 因此,BL2 也会使用 BL32 镜像的 entrypoint。 用于进入 BL32 的 Saved Processor Status Register(SPSR)的值不是由 BL2 确定的,它由 BL31 内的 Secure-EL1 Payload Dispatcher (SPD) 初始化,SPD 负责管理与 BL32 的交互。此信息将传递给 BL31。
# BL33(Non-trusted Fireware) image load
BL2 将 BL33 镜像(e.g. UEFI or other test or boot software)从平台存储设备加载到由平台定义的非安全内存中。
一旦安全状态初始化完成,BL2 依靠 EL3 Runtime Software 将控制权传递给 BL33。 因此,BL2 使用正常世界的镜像入口和保存程序状态寄存器(SPSR)填充平台指定的存储区域。entrypoint 是 BL33 镜像的加载地址。 SPSR 按照 PSCI PDD 中的规定确定(PSCI 5.13 节)。 此信息将传递给 EL3runtime software。
# AArch64 BL31(EL3 Runtime Software) execution
BL2 执行继续如下:
BL2 通过产生 SMC 异常将控制权传递回 BL1,并给 BL1 提供 BL31 入口点。 SMC 异常由 BL1 阶段 install 的 SMC exception handler 来处理。
BL1 关闭 MMU 并刷 Cache。清 SCTLR_EL3.M/ I / C 位,将 D-cache 刷新到 point of coherency 并使 TLB 无效。
BL1 在 EL3 的指定入口地址将控制权传递给 BL31。
# 三、源代码分析
# (一)BL1(Trusted Boot ROM )分析
BL1 启动最早的 ROM,是在 CPU 的 ROM 里不是和 BIOS 一起,是一起的信任根。BL1 主要目的是建立 Trusted SRAM、exception vector、初始化串口 console 等等。然后找到并验证 BL2(验签 CSF 头),然后跳过去。
入口点:bl1_entrypoint.S:
1 2 3 4 5 6 7 8 9 func bl1_entrypoint .... bl bl1_early_platform_setup bl bl1_plat_arch_setup .... bl bl1_main .... b el3_exit endfunc bl1_entrypoint
# (二)BL2(Trusted Boot Firmware )分析
# 1、功能概要
BL2 主要负责对其他所有 BL 进行认证和加载,并执行 BL31, 该函数主要实现将 BL3x 的 image 加载 RAM 中,并通过 smc 调用执行 BL1 中指定的 smc handle 将 CPU 的全向交给 BL31。
# 2、主过程
各部分代码分析已在注释中给出
# BL2_entrypoint.S
BL2 入口位于 bl2/aarch64/bl2_entrypoint.S
中,BL2_entrypoint 是 BL2 的入口,前半部分主要进行一系列初始化工作,然后通过 BL2_main () 加载 BL3x 镜像到 RAM 中,最后通过 SMC 调用执行 BL1 中指定的 smc handler 将 CPU 执行权交给 BL31。
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 .globl bl2_entrypoint func bl2_entrypoint mov x20, x1 /* x1保存了内存布局信息 */ /* 设置异常处理函数 */ adr x0, early_exceptions msr vbar_el1, x0 isb /* --------------------------------------------- * 使能异常 * --------------------------------------------- */ msr daifclr, #DAIF_ABT_BIT /* --------------------------------------------- * 使能指令缓存,使能堆栈数据访问对齐检查 * --------------------------------------------- */ mov x1, #(SCTLR_I_BIT | SCTLR_A_BIT | SCTLR_SA_BIT) mrs x0, sctlr_el1 orr x0, x0, x1 msr sctlr_el1, x0 isb /* --------------------------------------------- * 失效内存 * --------------------------------------------- */ adr x0, __RW_START__ adr x1, __RW_END__ sub x1, x1, x0 bl inv_dcache_range /* --------------------------------------------- * BSS内存初始化 * --------------------------------------------- */ ldr x0, =__BSS_START__ ldr x1, =__BSS_SIZE__ bl zeromem16 #if USE_COHERENT_MEM ldr x0, =__COHERENT_RAM_START__ ldr x1, =__COHERENT_RAM_UNALIGNED_SIZE__ bl zeromem16 #endif /* -------------------------------------------- * 设置SP指针 * -------------------------------------------- */ bl plat_set_my_stack /* --------------------------------------------- * 串口初始化,更新内存布局信息,并初始化页表使能mmu * --------------------------------------------- */ mov x0, x20 bl bl2_early_platform_setup bl bl2_plat_arch_setup /* --------------------------------------------- * 跳转到主函数(通过SMC执行下一级BL不会返回) * --------------------------------------------- */ bl bl2_main /* --------------------------------------------- * 下面的代码不会执行 * --------------------------------------------- */ no_ret plat_panic_handler endfunc bl2_entrypoint
# BL2_main
bl2_main 为 bl2 的主程序,位于 bl2/bl2_main.c 中,安全启动的最重要两步在这个函数中完成:初始化硬件和找到 BL31。
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 void bl2_main(void) { entry_point_info_t *next_bl_ep_info; /* 输出提示信息 */ NOTICE("BL2: %s\n", version_string); NOTICE("BL2: %s\n", build_message); /* Perform remaining generic architectural setup in S-EL1 */ /* 初始化,这里开启FP/SIMD的访问权限 */ bl2_arch_setup(); #if PSA_FWU_SUPPORT fwu_init(); #endif /* PSA_FWU_SUPPORT */ crypto_mod_init(); //初始化加密库,加密库可以用于校验签名和哈希 /* Initialize authentication module */ auth_mod_init(); //初始化认证模块 /* Initialize the Measured Boot backend */ bl2_plat_mboot_init(); //初始化 measured boot后端 /* Initialize boot source */ bl2_plat_preload_setup(); //初始化镜像解析模块(img_parser_mod),用于校验镜像完整性以及从镜像中提取内容 /*加载后续引导加载程序映像。*/ next_bl_ep_info = bl2_load_images(); /*拆除 Measured Boot 后端*/ bl2_plat_mboot_finish(); #if !BL2_AT_EL3 && !ENABLE_RME #ifndef __aarch64__ /* * 对于 AArch32 状态,BL1 和 BL2 共享 MMU 设置。 * 鉴于 BL2 不映射 BL1 区域,MMU 需要 * 被禁用以返回 BL1。 */ disable_mmu_icache_secure(); #endif /* !__aarch64__ */ console_flush(); //控制台刷新 #if ENABLE_PAUTH /* * 在运行下一个引导映像之前禁用指针身份验证 */ pauth_disable_el1(); #endif /* ENABLE_PAUTH */ /* 调用smc指令,触发在bl1中设定的smc异常中断处理函数,跳转到bl31 */ smc(BL1_SMC_RUN_IMAGE, (unsigned long)next_bl_ep_info, 0, 0, 0, 0, 0, 0); #else /* if BL2_AT_EL3 || ENABLE_RME */ NOTICE("BL2: Booting " NEXT_IMAGE "\n"); print_entry_point_info(next_bl_ep_info); console_flush(); #if ENABLE_PAUTH /* * 在运行下一个引导映像之前禁用指针身份验证 */ pauth_disable_el3(); #endif /* ENABLE_PAUTH */ bl2_run_next_image(next_bl_ep_info); #endif /* BL2_AT_EL3 && ENABLE_RME */ }
下面依次对 bl2_main 中的安全模块进行分析:
# crypto_mod_init()
主要初始化加密库,加密库可以用于校验签名和哈希,跟进 crypto_mod_init()
在 crypto_mod.h 中定义了 crypto_lib_desc_s
结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct crypto_lib_desc_s { const char *name; void (*init)(void ); int (*verify_signature)( void *data_ptr, unsigned int data_len, void *sig_ptr, unsigned int sig_len, void *sig_alg, unsigned int sig_alg_len, void *pk_ptr, unsigned int pk_len); int (*verify_hash)( void *data_ptr, unsigned int data_len, void *digest_info_ptr, unsigned int digest_info_len); } crypto_lib_desc_t ;
通过 REGISTER_CRYPTO_LIB
宏实现一个名为 crypto_lib_desc
类型为 crypto_lib_desc_t
结构体。宏实现如下:
1 2 3 4 5 6 7 #define REGISTER_CRYPTO_LIB(_name, _init, _verify_signature, _verify_hash) \ const crypto_lib_desc_t crypto_lib_desc = { \ .name = _name, \ .init = _init, \ .verify_signature = _verify_signature, \ .verify_hash = _verify_hash \ }
此模块通过操作 crypto_lib_desc 变量,实现模块初始化、校验签名、校验哈希。函数声明如下:
1 2 3 4 5 6 7 8 9 10 11 /* 模块初始化 */ void crypto_mod_init(void); /* 校验签名 */ int crypto_mod_verify_signature(void *data_ptr, unsigned int data_len, void *sig_ptr, unsigned int sig_len, void *sig_alg, unsigned int sig_alg_len, void *pk_ptr, unsigned int pk_len); /* 校验哈希值 */ int crypto_mod_verify_hash(void *data_ptr, unsigned int data_len, void *digest_info_ptr, unsigned int digest_info_len);
# auth_mod_init()
auth_mod 实现了一个校验镜像的模型,此模型通过结构体 auth_img_desc_t
描述,跟进 auth_mod_init,
在 auth.mod.h 中定义了 auth_img_desc_s 结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct auth_img_desc_s { /* 镜像的ID,标志是哪一个镜像 */ unsigned int img_id; /* 镜像类型(Binary、证书等) */ img_type_t img_type; /* 父镜像,保存了认证当前镜像的公钥、哈希等 */ const struct auth_img_desc_s *parent; /* 认证当前镜像的方法 */ auth_method_desc_t img_auth_methods[AUTH_METHOD_NUM]; /* 用于校验子镜像的公钥、哈希等 */ auth_param_desc_t authenticated_data[COT_MAX_VERIFIED_PARAMS]; } auth_img_desc_t;
并定义一个宏 REGISTER_COT
,用于注册 auth_img_desc_t
数组
1 2 3 4 #define REGISTER_COT(_cot) \ const auth_img_desc_t *const cot_desc_ptr = \ (const auth_img_desc_t *const)&_cot[0]; \ unsigned int auth_img_flags[sizeof(_cot)/sizeof(_cot[0])]
auth_mod_verify_img
通过 img_id
访问 cot_desc_ptr
数组,找到对应的镜像描述符 auth_method_desc_t
,即可知道当前镜像的认证方式,访问父节点找到签名的公钥或哈希,即可认证当前镜像是否合法。在认证完当前镜像后,从镜像中解析出公钥哈希等放入当前的镜像描述符中,便于对下一级镜像校验
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 int auth_mod_verify_img(unsigned int img_id, void *img_ptr, unsigned int img_len) { const auth_img_desc_t *img_desc = NULL; const auth_method_desc_t *auth_method = NULL; void *param_ptr; unsigned int param_len; int rc, i; /* 根据img_id获取镜像描述符 */ img_desc = &cot_desc_ptr[img_id]; /* 校验镜像完整性 */ rc = img_parser_check_integrity(img_desc->img_type, img_ptr, img_len); return_if_error(rc); /* 根据镜像描述符的仍正方式对镜像进行认证 */ for (i = 0 ; i < AUTH_METHOD_NUM ; i++) { auth_method = &img_desc->img_auth_methods[i]; switch (auth_method->type) { case AUTH_METHOD_NONE:/* 不需要认证 */ rc = 0; break; case AUTH_METHOD_HASH:/* 哈希认证 */ rc = auth_hash(&auth_method->param.hash, img_desc, img_ptr, img_len); break; case AUTH_METHOD_SIG:/* 签名认证 */ rc = auth_signature(&auth_method->param.sig, img_desc, img_ptr, img_len); break; case AUTH_METHOD_NV_CTR:/* Non-Volatile counter认证? */ rc = auth_nvctr(&auth_method->param.nv_ctr, img_desc, img_ptr, img_len); break; default: /* 未知认证类型,报错 */ rc = 1; break; } return_if_error(rc); } /* 从镜像中解析出公钥哈希等,以便对下一级镜像进行认证 */ for (i = 0 ; i < COT_MAX_VERIFIED_PARAMS ; i++) { if (img_desc->authenticated_data[i].type_desc == NULL) { continue; } /* 通过镜像解析器从镜像中提取内容 */ rc = img_parser_get_auth_param(img_desc->img_type, img_desc->authenticated_data[i].type_desc, img_ptr, img_len, ¶m_ptr, ¶m_len); return_if_error(rc); /* 异常检查 防止从镜像中解析出的数据字节数大于镜像描述符中的字节数 出现内存访问溢出 */ if (param_len > img_desc->authenticated_data[i].data.len) { return 1; } /* 把解析出的内容拷贝到镜像描述符中,便于解析下一级BL */ memcpy((void *)img_desc->authenticated_data[i].data.ptr, (void *)param_ptr, param_len); } /* 标记镜像以认证过 */ auth_img_flags[img_desc->img_id] |= IMG_FLAG_AUTHENTICATED; return 0; }
# BL2_image_load_v2.c
该函数用来加载 bl3x 的 image 到 RAM 中,返回一个具有 image 入口信息的变量。smc handle 根据该变量跳转到 bl31 进行执行。
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 struct entry_point_info_t *bl2_load_images (void ) { bl_params_t *bl2_to_next_bl_params;bl_load_info_t *bl2_load_info;const bl_load_info_node_t *bl2_node_info;int plat_setup_done = 0 ;int err;bl2_load_info = plat_get_bl_image_load_info(); assert(bl2_load_info); assert(bl2_load_info->head); assert(bl2_load_info->h.type == PARAM_BL_LOAD_INFO); assert(bl2_load_info->h.version >= VERSION_2); bl2_node_info = bl2_load_info->head; while (bl2_node_info) {if (bl2_node_info->image_info->h.attr & IMAGE_ATTRIB_PLAT_SETUP) {if (plat_setup_done) {WARN("BL2: Platform setup already done!!\n" ); } else { INFO("BL2: Doing platform setup\n" ); bl2_platform_setup(); plat_setup_done = 1 ; } } if (!(bl2_node_info->image_info->h.attr & IMAGE_ATTRIB_SKIP_LOADING)) {INFO("BL2: Loading image id %d\n" , bl2_node_info->image_id); err = load_auth_image(bl2_node_info->image_id, bl2_node_info->image_info); if (err) {ERROR("BL2: Failed to load image (%i)\n" , err); plat_error_handler(err); } } else { INFO("BL2: Skip loading image id %d\n" , bl2_node_info->image_id); } err = bl2_plat_handle_post_image_load(bl2_node_info->image_id); if (err) {ERROR("BL2: Failure in post image load handling (%i)\n" , err); plat_error_handler(err); } bl2_node_info = bl2_node_info->next_load_info; } bl2_to_next_bl_params = plat_get_next_bl_params(); assert(bl2_to_next_bl_params); assert(bl2_to_next_bl_params->head); assert(bl2_to_next_bl_params->h.type == PARAM_BL_PARAMS); assert(bl2_to_next_bl_params->h.version >= VERSION_2); plat_flush_next_bl_params(); return bl2_to_next_bl_params->head->ep_info;}
# 四、分析结论
结合 ATF 整个信任链条建立的流程图,我们了解到可信启动中的安全模块和可信机制,从作为信任根的 BL1 开始,逐步进行初始化和加载镜像,最后来到 BL33,后面就是 OS 了。
最后引用一张 ATF 的 UEFI 启动流程进行更直观展示:
以上仅是对 ATF 可信启动机制的简要分析,而对于 ATF 的更深层次技术需要更多信息搜集和整理研究。
# 五、参考文章
https://zhuanlan.zhihu.com/p/391101179
https://github.com/hardenedlinux/embedded-iot_profile/blob/master/docs/arm64/arm-trusted-firmware 分析.md
https://blog.csdn.net/puyoupuyou/article/details/109506419?spm=1001.2101.3001.6650.15&utm_medium=distribute.pc_relevant.none-task-blog-2 default BlogCommendFromBaiduRate-15.topblog&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2 defaultBlogCommendFromBaidu Rate-15.topblog&utm_relevant_index=20