,如何配置DMA传输以保护敏感数据,防止非安全世界访问。)
TrustZone 下的 DMA 安全传输。说实话我早年做 MTK 平台驱动时对 TrustZone 的理解也就停留在「有个安全世界有个非安全世界」。直到有一次客户的指纹数据在 DMA 传输过程中被非安全世界的恶意程序嗅探到了……嗯那场面相当尴尬。从那以后我彻底把 TrustZone DMA 的配置刻进了 DNA。为什么普通 DMA 不安全你想想看普通的 DMA 控制器默认是挂在非安全总线上的。非安全世界的 CPU、外设都能直接访问 DMA 的寄存器。这意味着什么非安全世界的代码可以篡改 DMA 的源地址、目的地址非安全世界的代码可以读取 DMA 正在传输的数据非安全世界的代码可以发起一个伪装的 DMA 请求把安全内存的数据搬到非安全内存说白了只要 DMA 控制器本身不受保护安全世界和非安全世界之间的墙就是纸糊的。⚠️ 重要提醒在 MTK 平台上默认情况下 DMA 控制器属于非安全外设。如果你不做任何配置安全世界发起的 DMA 传输非安全世界是可以「围观」的。MTK TrustZone 下的 DMA 安全架构MTK 的 TrustZone 实现在硬件层面做了几件事来保护 DMA 传输DMA 控制器本身被配置为安全外设——只有安全世界的软件才能访问它的寄存器DMA 的传输通道支持安全/非安全属性标记——每个 DMA 通道都可以独立配置为安全通道或非安全通道内存访问权限检查——DMA 在访问内存时硬件会检查当前传输的安全属性是否与目标内存区域匹配我个人习惯把这三层保护叫做「三道锁」锁外设、锁通道、锁内存。在 OP-TEE 中配置安全 DMA好咱们直接上代码。以下是我在 MTK 平台上配置安全 DMA 的典型流程运行在 OP-TEE 中。// 1. 获取 DMA 控制器的安全访问权限 // 在 OP-TEE 的 device tree 中需要将 DMA 控制器标记为安全外设 // 通常在 dts 中这样配置 // dma0 { // status okay; // secure-status okay; // 关键标记为安全外设 // }; // 2. 在 OP-TEE 驱动中初始化 DMA 通道 static tee_result_t secure_dma_channel_init(void) { uint32_t ch_num 0; // 我个人习惯先检查 DMA 控制器的安全状态 uint32_t ctrl_secure io_read32(DMA_BASE DMA_SECURE_CTRL); if (!(ctrl_secure DMA_SECURE_EN)) { EMSG(DMA controller is NOT in secure mode!); return TEE_ERROR_SECURITY; } // 配置通道为安全通道 // 在 MTK 平台上每个通道有一个安全配置寄存器 io_write32(DMA_BASE DMA_CH_SECURE(ch_num), DMA_CH_SECURE_EN | DMA_CH_SECURE_IRQ_EN); // 配置源地址和目的地址必须是安全内存区域 // 注意这里的地址必须是 TEE 安全堆或安全共享内存中的地址 io_write32(DMA_BASE DMA_CH_SRC(ch_num), (uint32_t)secure_src_buf); io_write32(DMA_BASE DMA_CH_DST(ch_num), (uint32_t)secure_dst_buf); // 配置传输长度和模式 io_write32(DMA_BASE DMA_CH_LEN(ch_num), transfer_len); io_write32(DMA_BASE DMA_CH_CTRL(ch_num), DMA_MODE_MEM2MEM | DMA_BURST_16 | DMA_WIDTH_32); return TEE_SUCCESS; } 我的经验配置 DMA 通道的安全属性时一定要先确认 DMA 控制器本身已经处于安全模式。我曾经踩过一个坑控制器没配成安全模式光配通道安全属性结果非安全世界照样能改通道配置。顺序很重要——先锁大门再锁房间。安全内存区域的分配DMA 要传输的数据必须放在安全世界能访问、非安全世界不能访问的内存区域。在 OP-TEE 中我们通常用两种方式分配安全内存内存类型分配方式特点TEE 安全堆内存malloc()或tee_pool_alloc()仅安全世界可访问非安全世界完全不可见安全共享内存register_shm()或mobj_mapped_shm_alloc()安全世界和非安全世界都可访问但需要显式注册我个人强烈建议敏感数据比如密钥、指纹模板、人脸特征一定要用 TEE 安全堆内存。安全共享内存虽然方便但它毕竟对非安全世界开了个口子容易出问题。防止非安全世界访问的额外措施除了硬件层面的配置软件层面也要做防护。我曾经在项目中遇到过一个问题DMA 传输完成后中断处理函数被非安全世界的代码劫持了。嗯这又是一个坑。所以我总结了几个必做的措施安全中断处理DMA 传输完成中断必须在安全世界处理不能让非安全世界碰。在 MTK 平台上需要将 DMA 的中断配置为安全中断FIQ。传输完成后立即清零DMA 传输完成后立即将源缓冲区和目的缓冲区清零。防止非安全世界通过 DMA 控制器的残留数据嗅探。校验传输完整性在安全世界对传输后的数据进行哈希校验确保数据没有被篡改。// 安全 DMA 传输完成后的清理工作 static void secure_dma_complete_handler(void) { // 1. 先校验数据完整性 uint32_t hash calculate_sha256(secure_dst_buf, transfer_len); if (hash ! expected_hash) { EMSG(DMA transfer integrity check FAILED!); // 触发安全处理流程 secure_error_handler(); return; } // 2. 立即清零缓冲区 memset(secure_src_buf, 0, transfer_len); memset(secure_dst_buf, 0, transfer_len); // 3. 清零 DMA 通道的配置寄存器 io_write32(DMA_BASE DMA_CH_SRC(ch_num), 0); io_write32(DMA_BASE DMA_CH_DST(ch_num), 0); io_write32(DMA_BASE DMA_CH_LEN(ch_num), 0); DMSG(Secure DMA transfer completed and cleaned up.); }⚠️ 特别注意不要以为 DMA 传输完了就万事大吉。DMA 控制器的寄存器里可能还残留着地址信息。非安全世界的攻击者可以通过读取 DMA 寄存器来推断安全内存的布局。所以清零寄存器这一步绝对不能省。调试与验证配置完成后怎么验证你的安全 DMA 真的安全了我一般做三件事从非安全世界尝试访问 DMA 控制器寄存器——应该返回全 F 或者触发异常从非安全世界尝试读取 DMA 正在传输的安全内存——应该读到全 0 或者触发异常从非安全世界尝试配置 DMA 通道——应该被硬件拒绝说白了你要站在攻击者的角度去测试。我当年就是这么干的——写一个非安全世界的测试程序各种尝试越界访问直到它彻底死心。总结一下MTK TrustZone 下的安全 DMA 配置核心就三件事把 DMA 控制器锁在安全世界里把 DMA 通道标记为安全通道把数据放在安全内存里传输完立刻擦屁股做到这三点你的敏感数据在 DMA 传输过程中就是安全的。非安全世界想看门都没有。