TEE-TA学习轨迹第三篇:动态加载TA的加载流程和验签流程

下面完全基于 OP-TEE 真实源码路径,结合已经分析过的线程模型、SMC 入口、异常向量、RPC 挂起/恢复等底层代码,从 TEE 内核视角,逐段拆解用户态 TA 从加载到运行、再到销毁的完整代码级流程,每个环节都对应核心代码片段与硬件原理。以后的学习以撸代码为主了,相信经过之前的理论知识学习,我们都掌握了基本理论知识。

一、入口层:为什么用户态 TA 走 Std SMC 入口

用户态 TA 的调用绝对不会走 Fast SMC 路径,必须走 vector_std_smc_entry(标准/可抢占 SMC),这是由 TA 的特性决定的:TA 可能执行耗时操作、可能发起 RPC、可能被中断抢占,需要完整的线程上下文调度支撑。

核心汇编入口代码(thread_optee_smc_a64.S)

LOCAL_FUNC vector_std_smc_entry , : , .identity_map
    readjust_pc              // 修正 ASLR 偏移,修正运行时虚拟地址
    sub  sp, sp, #THREAD_SMC_ARGS_SIZE
    store_xregs sp, THREAD_SMC_ARGS_X0, 0, 7   // 保存 x0~x7 到栈上参数区
    mov  x0, sp
    bl   thread_handle_std_smc    // 进入 C 语言主分发函数

    // 返回路径:加载结果到寄存器,SMC 切回 EL3
    load_xregs sp, THREAD_SMC_ARGS_X0, 0, 4
    add  sp, sp, #THREAD_SMC_ARGS_SIZE
    smc  #0
    b    .   // 异常兜底,SMC 不应返回
END_FUNC vector_std_smc_entry


uint32_t thread_handle_std_smc(uint32_t a0, uint32_t a1, uint32_t a2,
			       uint32_t a3, uint32_t a4, uint32_t a5,
			       uint32_t a6 __unused, uint32_t a7 __maybe_unused)
{
    	uint32_t rv = OPTEE_SMC_RETURN_OK;
    
    	thread_check_canaries();
    
    	if (IS_ENABLED(CFG_NS_VIRTUALIZATION) && virt_set_guest(a7))
    		return OPTEE_SMC_RETURN_ENOTAVAIL;
    
    	/*
    	 * thread_resume_from_rpc() and thread_alloc_and_run() only return
    	 * on error. Successful return is done via thread_exit() or
    	 * thread_rpc().
    	 */
    	if (a0 == OPTEE_SMC_CALL_RETURN_FROM_RPC) {
    		thread_resume_from_rpc(a3, a1, a2, a4, a5);
    		rv = OPTEE_SMC_RETURN_ERESUME;
    	} else {
    		thread_alloc_and_run(a0, a1, a2, a3, 0, 0);
    		rv = OPTEE_SMC_RETURN_ETHREAD_LIMIT;
    	}
    
    	if (IS_ENABLED(CFG_NS_VIRTUALIZATION))
    		virt_unset_guest();
    
    	return rv;
}

与 Fast SMC 的本质代码差异

维度
Fast SMC(内置TA用)
Std SMC(用户态TA用)
栈使用
直接用异常栈,不分配线程
需要分配独立线程栈,支持调度
中断屏蔽
全程关中断,原子执行
可开中断,支持抢占
RPC 能力
不支持(不能挂起)
原生支持,可随时挂起切回 REE
返回方式
执行完直接 SMC 返回
线程挂起/恢复调度,可多次进出
对应你之前分析的线程模型:Fast SMC 是“异常栈上直接跑”,无线程概念;Std SMC 是“分配独立线程 + 栈”,支持完整的挂起、恢复、抢占,这是用户态 TA 调度的底层基础。

非安全 → 安全,SMC 发起调用时的 a0 ~ a7

uint32_t thread_handle_std_smc(uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5,uint32_t a6 __unused, uint32_t a7 __maybe_unused) 的入参结构体,汇编入口 vector_std_smc_entry 里 store_xregs 保存的 x0~x7 就一一对应这里的 a0~a7。对应代码
//调入申请thread 并且 运行
void thread_alloc_and_run(uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3,
			  uint32_t a4, uint32_t a5)
{
	__thread_alloc_and_run(a0, a1, a2, a3, a4, a5, 0, 0,
			       thread_std_smc_entry, 0);
}

static void __thread_alloc_and_run(uint32_t a0, uint32_t a1, uint32_t a2,
				   uint32_t a3, uint32_t a4, uint32_t a5,
				   uint32_t a6, uint32_t a7,
				   void *pc, uint32_t flags)
{
    	struct thread_core_local *l = thread_get_core_local();
    	bool found_thread = false;
    	size_t n = 0;
    
    	assert(l->curr_thread == THREAD_ID_INVALID);
    
    	thread_lock_global();
    
    	for (n = 0; n < CFG_NUM_THREADS; n++) {
    		if (threads[n].state == THREAD_STATE_FREE) {
    			threads[n].state = THREAD_STATE_ACTIVE;
    			found_thread = true;
    			break;
    		}
    	}
    
    	thread_unlock_global();
    
    	if (!found_thread)
    		return;
    
    	l->curr_thread = n;
    
    	threads[n].flags = flags;
    	init_regs(threads + n, a0, a1, a2, a3, a4, a5, a6, a7, pc); //初始化要执行线程cpu上下文
    #ifdef CFG_CORE_PAUTH
    	/*
    	 * Copy the APIA key into the registers to be restored with
    	 * thread_resume().
    	 */
    	threads[n].regs.apiakey_hi = threads[n].keys.apia_hi;
    	threads[n].regs.apiakey_lo = threads[n].keys.apia_lo;
    #endif
    
    	thread_lazy_save_ns_vfp();
    
    	l->flags &= ~THREAD_CLF_TMP;
    	thread_resume(&threads[n].regs);
    	/*NOTREACHED*/
    	panic();
}

#ifdef ARM64
static void init_regs(struct thread_ctx *thread, uint32_t a0, uint32_t a1,
		      uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5,
		      uint32_t a6, uint32_t a7, void *pc)
{
	thread->regs.pc = (uint64_t)pc;

	/*
	 * Stdcalls starts in SVC mode with masked foreign interrupts, masked
	 * Asynchronous abort and unmasked native interrupts.
	 */
	thread->regs.cpsr = SPSR_64(SPSR_64_MODE_EL1, SPSR_64_MODE_SP_EL0,
				THREAD_EXCP_FOREIGN_INTR | DAIFBIT_ABT);
	/* Reinitialize stack pointer */
	thread->regs.sp = thread->stack_va_end;

	/*
	 * Copy arguments into context. This will make the
	 * arguments appear in x0-x7 when thread is started.
	 */
	thread->regs.x[0] = a0;
	thread->regs.x[1] = a1;
	thread->regs.x[2] = a2;
	thread->regs.x[3] = a3;
	thread->regs.x[4] = a4;
	thread->regs.x[5] = a5;
	thread->regs.x[6] = a6;
	thread->regs.x[7] = a7;

	/* Set up frame pointer as per the Aarch64 AAPCS */
	thread->regs.x[29] = 0;
}
#endif /*ARM64*/

//通过汇编运行线程
/* void thread_resume(struct thread_ctx_regs *regs) */
FUNC thread_resume , :
	load_xregs x0, THREAD_CTX_REGS_SP, 1, 3
	load_xregs x0, THREAD_CTX_REGS_X4, 4, 30
	mov	sp, x1
	msr	elr_el1, x2
	msr	spsr_el1, x3
	ldr	x1, [x0, THREAD_CTX_REGS_TPIDR_EL0]
	msr	tpidr_el0, x1

#if defined(CFG_TA_PAUTH) || defined(CFG_CORE_PAUTH)
	load_xregs x0, THREAD_CTX_REGS_APIAKEY_HI, 1, 2
	write_apiakeyhi	x1
	write_apiakeylo	x2
#endif
	b_if_spsr_is_el0 w3, 1f

#if defined(CFG_CORE_PAUTH) || defined(CFG_TA_PAUTH)
	/* SCTLR or the APIA key has changed */
	isb
#endif
	load_xregs x0, THREAD_CTX_REGS_X1, 1, 3
	ldr	x0, [x0, THREAD_CTX_REGS_X0]
	return_from_exception

1:
	load_xregs x0, THREAD_CTX_REGS_X1, 1, 3
	ldr	x0, [x0, THREAD_CTX_REGS_X0]

	msr	spsel, #1
	store_xregs sp, THREAD_CORE_LOCAL_X0, 0, 1
	b	eret_to_el0
END_FUNC thread_resume

1. a0:SMC 功能号(Function ID),最核心的路由字段
这是所有 SMC 调用的第一个参数,决定了本次调用的类型、归属、具体子功能,是 EL3 和安全内核分发请求的唯一依据。
按照 SMCCC 标准,32 位功能号的位域结构如下:
位段
含义
说明
Bit 31
调用位宽
0 = SMC32(32位约定),1 = SMC64(64位约定)
Bit 30
调用类型
0 = 标准可抢占调用(Yielding),1 = 快速原子调用(Fast)
Bits 29:24
服务归属号(OEN)
标识服务提供方,OP-TEE 固定为
0x3E
(可信OS域)
Bits 23:0
子功能号
具体的命令编号,比如打开会话、调用命令、RPC返回等
OP-TEE 中用户态 TA 相关的调用,功能号特征为:SMC32 + Yielding + OEN 0x3E + 子功能号,内核通过解析 a0 就能判断本次调用是「打开会话」「调用命令」还是「RPC返回」。
2. a1 ~ a7:7 个通用入参
具体含义完全由 a0 的功能号决定,OP-TEE 主要分两种传参模式:
模式 A:共享内存传参(CALL_WITH_ARG
打开会话、调用命令、关闭会话等复杂操作都走这个模式,也是你之前分析 TA 加载场景的默认模式。
寄存器只传递「共享内存的位置信息」,真正的 UUID、参数、命令都放在共享内存的消息结构体里。
参数
含义
示例(OpenSession 场景)
a1
参数结构体的物理地址
非安全侧分配的共享内存物理地址,指向
struct optee_msg_arg
,里面包含 UUID、param 等完整信息
a2
参数结构体的总大小
用于安全侧做边界校验,防止越界访问
a3 ~ a7
保留/扩展
基本不用,标记为
__unused
UUID 并不直接放在寄存器里传递,而是打包在共享内存中,a1 只传一个物理地址。安全侧必须先校验这个地址属于合法的共享内存区域,才能映射访问,这是跨世界调用的核心安全防线。
模式 B:寄存器直传参(CALL_WITH_REG
用于简单、短参数的快速调用,比如获取版本号、简单控制命令,参数直接放在寄存器里,不需要共享内存,性能更高。
此时 a1~a7 直接对应命令的 7 个入参,按功能号约定依次使用

二、分发层:SMC 解析与会话查找

进入 C 层后,首先由
统一解析 SMC 功能号,再分发到 TA 会话管理模块。启动TA
thread_handle_std_smc 
      thread_alloc_and_run 
            __thread_alloc_and_run 
                init_regs
                        thread_resume

第1步:调用方传入入口函数

你看到的这行调用,是线程分配的真实入口:
__thread_alloc_and_run(a0, a1, a2, a3, a4, a5, 0, 0, thread_std_smc_entry, 0);
  • 前 8 个参数:对应 SMC 寄存器的 a0~a7,是要传递给线程的入参;
  • 第 9 个参数:线程启动后的第一条指令的地址,也就是 thread_std_smc_entry 这个汇编函数的地址;
  • 第 10 个参数:线程初始标志位,这里传 0。

第2步:内部把入口地址写入线程上下文

__thread_alloc_and_run 内部会调用 init_regs 初始化线程寄存器快照,把传入的入口地址填到 pc 字段:
// 简化示意
void __thread_alloc_and_run(uint32_t a0, ..., uintptr_t entry, uint32_t flags)
{
    // 找到空闲线程槽位
    struct thread_ctx *t = &threads[n];

    // 把入口地址、入参全部写入线程上下文结构体
    init_regs(&t->regs, a0, a1, a2, a3, a4, a5, entry);
    t->flags = flags;

    // ... 其他初始化 ...

    // 启动线程
    thread_resume(&t->regs);
}
init_regs 里核心的一行就是:
regs->pc = entry;  // 把传入的入口地址赋值给程序计数器字段
regs->cpsr = SPSR_EL1_MODE; // 运行在 S-EL1 内核态
到这里,还只是往内存里写数值,没有真正执行。

第3步:thread_resume 硬件跳转

跟之前分析的完全一致:thread_resume 把上下文结构体里的值全部回灌到 CPU 寄存器,把 regs->pc 写入 ELR_EL1,最终执行 ERET 指令。
CPU 硬件自动跳转到 ELR_EL1 指向的地址,也就是 thread_std_smc_entry,线程正式启动,运行在 S-EL1 内核态,使用线程私有栈。

thread_std_smc_entry 是汇编层的统一线程入口,它不直接处理业务,只做参数整理和栈切换,然后通过「全局函数指针」跳转到上层业务处理函数。
// 自 optee_os\core\arch\arm\kernel\thread_optee_smc_a64.S
FUNC thread_std_smc_entry , :
        // 此时栈已经是线程私有内核栈
    // 把入参整理成结构体指针,调用 C 层入口
	bl	__thread_std_smc_entry
	mov	w20, w0	/* Save return value for later */

	/* Mask all maskable exceptions before switching to temporary stack */
	msr	daifset, #DAIFBIT_ALL
	bl	thread_get_tmp_sp
	mov	sp, x0

	bl	thread_state_free

	ldr	x0, =TEESMC_OPTEED_RETURN_CALL_DONE
	mov	w1, w20
	mov	x2, #0
	mov	x3, #0
	mov	x4, #0
	smc	#0
	/* SMC should not return */
	panic_at_smc_return
END_FUNC thread_std_smc_entry

第4步:C 层分发,调用注册好的业务回调

__thread_std_smc_entry 是底层线程框架和上层业务的桥梁,它不直接写死业务函数,而是调用全局注册的处理函数指针:
// 自optee_os\core\arch\arm\kernel\thread_optee_smc.c 
/*
 * Helper routine for the assembly function thread_std_smc_entry()
 *
 * Note: this function is weak just to make it possible to exclude it from
 * the unpaged area.
 */
uint32_t __weak __thread_std_smc_entry(uint32_t a0, uint32_t a1, uint32_t a2,
				       uint32_t a3, uint32_t a4 __unused,
				       uint32_t a5 __unused)
{
	if (IS_ENABLED(CFG_NS_VIRTUALIZATION))
		virt_on_stdcall();

	return std_smc_entry(a0, a1, a2, a3);
}

static uint32_t std_smc_entry(uint32_t a0, uint32_t a1, uint32_t a2,
			      uint32_t a3 __unused)
{
    	const bool with_rpc_arg = true;
    
    	switch (a0) {
    	case OPTEE_SMC_CALL_WITH_ARG:  ///走这个分支比较多
    		return std_entry_with_parg(reg_pair_to_64(a1, a2),
    					   !with_rpc_arg);
    	case OPTEE_SMC_CALL_WITH_RPC_ARG:
    		return std_entry_with_parg(reg_pair_to_64(a1, a2),
    					   with_rpc_arg);
    	case OPTEE_SMC_CALL_WITH_REGD_ARG:
    		return std_entry_with_regd_arg(reg_pair_to_64(a1, a2), a3);
    	default:
    		EMSG("Unknown SMC 0x%"PRIx32, a0);
    		return OPTEE_SMC_RETURN_EBADCMD;
    	}
}
///参数处理函数optee_os\core\arch\arm\kernel\thread_optee_smc.c
static uint32_t std_entry_with_parg(paddr_t parg, bool with_rpc_arg)
{
    	size_t sz = sizeof(struct optee_msg_arg);
    	struct optee_msg_arg *rpc_arg = NULL;
    	struct optee_msg_arg *arg = NULL;
    	struct mobj *mobj = NULL;
    	size_t num_params = 0;
    	uint32_t rv = 0;
    
    	/* Check if this region is in static shared space */
    	if (core_pbuf_is(CORE_MEM_NSEC_SHM, parg, sz)) {
    		if (!IS_ALIGNED_WITH_TYPE(parg, struct optee_msg_arg))
    			goto bad_addr;
    
    		arg = phys_to_virt(parg, MEM_AREA_NSEC_SHM,
    				   sizeof(struct optee_msg_arg));
    		if (!arg)
    			goto bad_addr;
    
    		num_params = READ_ONCE(arg->num_params);
    		if (num_params > OPTEE_MSG_MAX_NUM_PARAMS)
    			return OPTEE_SMC_RETURN_EBADADDR;
    
    		sz = OPTEE_MSG_GET_ARG_SIZE(num_params);
    		if (with_rpc_arg) {
    			rpc_arg = (void *)((uint8_t *)arg + sz);
    			sz += OPTEE_MSG_GET_ARG_SIZE(THREAD_RPC_MAX_NUM_PARAMS);
    		}
    		if (!core_pbuf_is(CORE_MEM_NSEC_SHM, parg, sz))
    			goto bad_addr;
    
    		return call_entry_std(arg, num_params, rpc_arg);
    	} else {
    		if (parg & SMALL_PAGE_MASK)
    			goto bad_addr;
    		/*
    		 * mobj_mapped_shm_alloc checks if parg resides in nonsec
    		 * ddr.
    		 */
    		mobj = mobj_mapped_shm_alloc(&parg, 1, 0, 0);
    		if (!mobj)
    			goto bad_addr;
    		if (with_rpc_arg)
    			rv = get_msg_arg(mobj, 0, &num_params, &arg, &rpc_arg);
    		else
    			rv = get_msg_arg(mobj, 0, &num_params, &arg, NULL);
    		if (!rv)
    			rv = call_entry_std(arg, num_params, rpc_arg);
    		mobj_put(mobj);
    		return rv;
    	}
    
    bad_addr:
    	EMSG("Bad arg address 0x%"PRIxPA, parg);
    	return OPTEE_SMC_RETURN_EBADADDR;
}

第5步:业务层注册回调,指向 tee_entry_std

上层 TA 管理子系统在启动时,会把自己的处理函数注册到这个全局回调表里:
// 业务层注册,和底层线程框架解耦optee_os\core\arch\arm\kernel\thread_optee_smc.c
static uint32_t call_entry_std(struct optee_msg_arg *arg, size_t num_params,
			       struct optee_msg_arg *rpc_arg)
{
    	struct thread_ctx *thr = threads + thread_get_id();
    	uint32_t rv = 0;
    
    	if (rpc_arg) {
    		/*
    		 * In case the prealloc RPC arg cache is enabled, clear the
    		 * cached object for this thread.
    		 *
    		 * Normally it doesn't make sense to have the prealloc RPC
    		 * arg cache enabled together with a supplied RPC arg
    		 * struct. But if it is we must use the supplied struct and
    		 * at the same time make sure to not break anything.
    		 */
    		if (IS_ENABLED(CFG_PREALLOC_RPC_CACHE) &&
    		    thread_prealloc_rpc_cache)
    			clear_prealloc_rpc_cache(thr);
    		thr->rpc_arg = rpc_arg;
    	}
    
    	if (tee_entry_std(arg, num_params))
    		rv = OPTEE_SMC_RETURN_EBADCMD;
    	else
    		rv = OPTEE_SMC_RETURN_OK;
    
    	thread_rpc_shm_cache_clear(&thr->shm_cache);
    	if (rpc_arg)
    		thr->rpc_arg = NULL;
    
    	if (rv == OPTEE_SMC_RETURN_OK &&
    	    !(IS_ENABLED(CFG_PREALLOC_RPC_CACHE) && thread_prealloc_rpc_cache))
    		clear_prealloc_rpc_cache(thr);
    
    	return rv;
}
optee_os\core\tee\entry_std.c
TEE_Result __weak tee_entry_std(struct optee_msg_arg *arg, uint32_t num_params)
{
	   return __tee_entry_std(arg, num_params);
}

/*
 * If tee_entry_std() is overridden, it's still supposed to call this
 * function.
 */
TEE_Result __tee_entry_std(struct optee_msg_arg *arg, uint32_t num_params)
{
	TEE_Result res = TEE_SUCCESS;

    	/* Enable foreign interrupts for STD calls */
    	thread_set_foreign_intr(true);
    	switch (arg->cmd) {
    	case OPTEE_MSG_CMD_OPEN_SESSION:
    		entry_open_session(arg, num_params);
    		break;
    	case OPTEE_MSG_CMD_CLOSE_SESSION:
    		entry_close_session(arg, num_params);
    		break;
    	case OPTEE_MSG_CMD_INVOKE_COMMAND:
    		entry_invoke_command(arg, num_params);
    		break;
    	case OPTEE_MSG_CMD_CANCEL:
    		entry_cancel(arg, num_params);
    		break;
#if defined(CFG_CORE_DYN_SHM) && !defined(CFG_CORE_FFA)
    	case OPTEE_MSG_CMD_REGISTER_SHM:
    		register_shm(arg, num_params);
    		break;
    	case OPTEE_MSG_CMD_UNREGISTER_SHM:
    		unregister_shm(arg, num_params);
    		break;
#endif
    	case OPTEE_MSG_CMD_DO_BOTTOM_HALF:
    		if (IS_ENABLED(CFG_CORE_ASYNC_NOTIF))
    			notif_deliver_event(NOTIF_EVENT_DO_BOTTOM_HALF);
    		else
    			goto err;
    		break;
    	case OPTEE_MSG_CMD_STOP_ASYNC_NOTIF:
    		if (IS_ENABLED(CFG_CORE_ASYNC_NOTIF))
    			notif_deliver_event(NOTIF_EVENT_STOPPED);
    		else
    			goto err;
    		break;
#ifdef CFG_CORE_DYN_PROTMEM
    	case OPTEE_MSG_CMD_GET_PROTMEM_CONFIG:
    		get_protmem_config(arg, num_params);
    		break;
 #ifdef CFG_CORE_FFA
    	case OPTEE_MSG_CMD_ASSIGN_PROTMEM:
    		assign_protmem(arg, num_params);
    		break;
 #else
    	case OPTEE_MSG_CMD_LEND_PROTMEM:
    		lend_protmem(arg, num_params);
    		break;
    	case OPTEE_MSG_CMD_RECLAIM_PROTMEM:
    		reclaim_protmem(arg, num_params);
    		break;
 #endif /*!CFG_CORE_FFA*/
 #endif /*CFG_CORE_DYN_PROTMEM*/
    	default:
    err:
    		EMSG("Unknown cmd 0x%x", arg->cmd);
    		res = TEE_ERROR_NOT_IMPLEMENTED;
    	}
    
    	return res;
}

所以最终 thread_std_smc_entry 等价于 tee_entry_std,正式进入我们熟悉的「共享内存解析 → 命令分发 → 会话创建 → TA 加载」业务逻辑。

第6步:TA 会话打开入口


//optee_os\core\tee\entry_std.c
static void entry_open_session(struct optee_msg_arg *arg, uint32_t num_params)
{
    	TEE_Result res = TEE_ERROR_GENERIC;
    	TEE_ErrorOrigin err_orig = TEE_ORIGIN_TEE;
    	uint32_t sess_id = 0;
    	TEE_Identity clnt_id = { };
    	TEE_UUID uuid = { };
    	struct tee_ta_param param = { };
    	size_t num_meta = 0;
    	uint64_t saved_attr[TEE_NUM_PARAMS] = { 0 };
    
    	res = get_open_session_meta(num_params, arg->params, &num_meta, &uuid,
    				    &clnt_id);
    	if (res != TEE_SUCCESS)
    		goto out;
    
    	res = copy_in_params(arg->params + num_meta, num_params - num_meta,
    			     &param, saved_attr);
    	if (res != TEE_SUCCESS)
    		goto cleanup_shm_refs;
    
    	res = tee_ta_open_session(&err_orig, &sess_id, &tee_open_sessions,
    				  &uuid, &clnt_id, TEE_TIMEOUT_INFINITE,
    				  &param);
    	if (res)
    		sess_id = 0;
    	copy_out_param(&param, num_params - num_meta, arg->params + num_meta,
    		       saved_attr);
    
    	/*
    	 * The occurrence of open/close session command is usually
    	 * un-predictable, using this property to increase randomness
    	 * of prng
    	 */
    	plat_prng_add_jitter_entropy(CRYPTO_RNG_SRC_JITTER_SESSION,
    				     &session_pnum);
    
  cleanup_shm_refs:
    	cleanup_shm_refs(saved_attr, &param, num_params - num_meta);
    
  out:
    	arg->session = sess_id;
    	arg->ret = res;
    	arg->ret_origin = err_orig;
}

第7步:加载elf

   ///optee_os\core\kernel\user_ta.c
        	
 TEE_Result tee_ta_complete_user_ta_session(struct tee_ta_session *s)
 {
    	struct user_ta_ctx *utc = to_user_ta_ctx(s->ts_sess.ctx);
    	TEE_Result res = TEE_SUCCESS;
    
    	/*
    	 * We must not hold tee_ta_mutex while allocating page tables as
    	 * that may otherwise lead to a deadlock.
    	 */
    	ts_push_current_session(&s->ts_sess);
    
    	res = ldelf_load_ldelf(&utc->uctx);
    	if (!res)
    		res = ldelf_init_with_ldelf(&s->ts_sess, &utc->uctx);
    
    	ts_pop_current_session();
    
    	mutex_lock(&tee_ta_mutex);
    
    	if (!res) {
    		utc->ta_ctx.is_initializing = false;
    	} else {
    		s->ts_sess.ctx = NULL;
    		TAILQ_REMOVE(&tee_ctxes, &utc->ta_ctx, link);
    		condvar_destroy(&utc->ta_ctx.busy_cv);
    		free_utc(utc);
    	}
    
    	/* The state has changed for the context, notify eventual waiters. */
    	condvar_broadcast(&tee_ta_init_cv);
    
    	mutex_unlock(&tee_ta_mutex);
    
    	return res;
  }
    
    /*ldelf_init_with_ldelf 内部做了三件关键的事:
    临时替换系统调用处理函数为 scall_handle_ldelf(ldelf 专属处理入口)
    构造用户态上下文,把目标 TA 的 UUID 作为入参放到用户栈上
    调用 thread_enter_user_mode 切入 S-EL0,ldelf 正式开始运行。*/
    
    /*
     * The ldelf return, log, panic syscalls have the same functionality and syscall
     * number as the user TAs'. To avoid unnecessary code duplication, the ldelf SVC
     * handler doesn't implement separate functions for these.
     */
static const struct syscall_entry ldelf_syscall_table[] = {
    	SYSCALL_ENTRY(syscall_sys_return),
    	SYSCALL_ENTRY(syscall_log),
    	SYSCALL_ENTRY(syscall_panic),
    	SYSCALL_ENTRY(ldelf_syscall_map_zi),
    	SYSCALL_ENTRY(ldelf_syscall_unmap),
    	SYSCALL_ENTRY(ldelf_syscall_open_bin),
    	SYSCALL_ENTRY(ldelf_syscall_close_bin),
    	SYSCALL_ENTRY(ldelf_syscall_map_bin),
    	SYSCALL_ENTRY(ldelf_syscall_copy_from_bin),
    	SYSCALL_ENTRY(ldelf_syscall_set_prot),
    	SYSCALL_ENTRY(ldelf_syscall_remap),
    	SYSCALL_ENTRY(ldelf_syscall_gen_rnd_num),
};
    
    
 TEE_Result ldelf_syscall_open_bin(const TEE_UUID *uuid, size_t uuid_size,
    				  uint32_t *handle)
 {
    	TEE_Result res = TEE_SUCCESS;
    	struct ts_session *sess = ts_get_current_session();
    	struct user_mode_ctx *uctx = to_user_mode_ctx(sess->ctx);
    	struct system_ctx *sys_ctx = sess->user_ctx;
    	struct bin_handle *binh = NULL;
    	uint8_t tag[FILE_TAG_SIZE] = { 0 };
    	unsigned int tag_len = sizeof(tag);
    	TEE_UUID *bb_uuid = NULL;
    	int h = 0;
    
    	res = BB_MEMDUP_USER(uuid, sizeof(*uuid), &bb_uuid);
    	if (res)
    		return res;
    
    	res = vm_check_access_rights(uctx,
    				     TEE_MEMORY_ACCESS_WRITE |
    				     TEE_MEMORY_ACCESS_ANY_OWNER,
    				     (uaddr_t)handle, sizeof(uint32_t));
    	if (res)
    		return res;
    
    	if (uuid_size != sizeof(*uuid))
    		return TEE_ERROR_BAD_PARAMETERS;
    
    	if (!sys_ctx) {
    		sys_ctx = calloc(1, sizeof(*sys_ctx));
    		if (!sys_ctx)
    			return TEE_ERROR_OUT_OF_MEMORY;
    		sess->user_ctx = sys_ctx;
    	}
    
    	binh = calloc(1, sizeof(*binh));
    	if (!binh)
    		return TEE_ERROR_OUT_OF_MEMORY;
    
    	if (is_user_ta_ctx(sess->ctx) || is_stmm_ctx(sess->ctx)) {
    		SCATTERED_ARRAY_FOREACH(binh->op, ta_stores,
    					struct ts_store_ops) {
    			DMSG("Lookup user TA ELF %pUl (%s)",
    			     (void *)bb_uuid, binh->op->description);
    
    			res = binh->op->open(bb_uuid, &binh->h);
    			DMSG("res=%#"PRIx32, res);
    			if (res != TEE_ERROR_ITEM_NOT_FOUND &&
    			    res != TEE_ERROR_STORAGE_NOT_AVAILABLE)
    				break;
    		}
    	} else if (is_sp_ctx(sess->ctx)) {
    		SCATTERED_ARRAY_FOREACH(binh->op, sp_stores,
    					struct ts_store_ops) {
    			DMSG("Lookup user SP ELF %pUl (%s)",
    			     (void *)bb_uuid, binh->op->description);
    
    			res = binh->op->open(bb_uuid, &binh->h);
    			DMSG("res=%#"PRIx32, res);
    			if (res != TEE_ERROR_ITEM_NOT_FOUND &&
    			    res != TEE_ERROR_STORAGE_NOT_AVAILABLE)
    				break;
    		}
    	} else {
    		res = TEE_ERROR_ITEM_NOT_FOUND;
    	}
    
    	if (res)
    		goto err;
    
    	res = binh->op->get_size(binh->h, &binh->size_bytes);
    	if (res)
    		goto err;
    	res = binh->op->get_tag(binh->h, tag, &tag_len);
    	if (res)
    		goto err;
    	binh->f = file_get_by_tag(tag, tag_len);
    	if (!binh->f)
    		goto err_oom;
    
    	h = handle_get(&sys_ctx->db, binh);
    	if (h < 0)
    		goto err_oom;
    	res = PUT_USER_SCALAR(h, handle);
    	if (res) {
    		handle_put(&sys_ctx->db, h);
    		goto err;
    	}
    
    	return TEE_SUCCESS;
    
    err_oom:
    	res = TEE_ERROR_OUT_OF_MEMORY;
    err:
    	bin_close(binh);
    	return res;
 }
    

第8步:验签elf

    
    //遍历对应的回调函数,与名字对应的回调
    SCATTERED_ARRAY_FOREACH(binh->op, ta_stores,
    	struct ts_store_ops) {
        DMSG("Lookup user TA ELF %pUl (%s)",
            (void *)bb_uuid, binh->op->description);
        
        res = binh->op->open(bb_uuid, &binh->h);
        DMSG("res=%#"PRIx32, res);
        if (res != TEE_ERROR_ITEM_NOT_FOUND &&
           res != TEE_ERROR_STORAGE_NOT_AVAILABLE)
        break;
    }
    
    #define SCATTERED_ARRAY_FOREACH(elem, array_name, element_type) \
    	for ((elem) = SCATTERED_ARRAY_BEGIN(array_name, element_type); \
    	     (elem) < SCATTERED_ARRAY_END(array_name, element_type); (elem)++)
    
    res = binh->op->open(bb_uuid, &binh->h);
    //调用到optee_os\core\kernel\ree_fs_ta.c
    #ifndef CFG_REE_FS_TA_BUFFERED
    REGISTER_TA_STORE(9) = {
        	.description = "REE",
        	.open = ree_fs_ta_open,
        	.get_size = ree_fs_ta_get_size,
        	.get_tag = ree_fs_ta_get_tag,
        	.read = ree_fs_ta_read,
        	.close = ree_fs_ta_close,
    };
    #endif
    static TEE_Result ree_fs_ta_open(const TEE_UUID *uuid,
    				 struct ts_store_handle **h)
    {
    	uint8_t next_uuid[sizeof(TEE_UUID)] = { };
    	struct ree_fs_ta_handle *handle;
    	uint8_t *next_uuid_ptr = NULL;
    	struct shdr *shdr = NULL;
    	struct mobj *mobj = NULL;
    	void *hash_ctx = NULL;
    	struct shdr *ta = NULL;
    	size_t ta_size = 0;
    	TEE_Result res = TEE_SUCCESS;
    	size_t offs = 0;
    	struct shdr_bootstrap_ta *bs_hdr = NULL;
    	struct shdr_encrypted_ta *ehdr = NULL;
    	size_t shdr_sz = 0;
    	uint32_t max_depth = UINT32_MAX;
    	struct ftmn ftmn = { };
    	unsigned int incr0_count = 0;
    
    	handle = calloc(1, sizeof(*handle));
    	if (!handle)
    		return TEE_ERROR_OUT_OF_MEMORY;
    
    	/* Request TA from tee-supplicant */
    	res = rpc_load(uuid, &ta, &ta_size, &mobj);
    	if (res != TEE_SUCCESS)
    		goto error;
    
    	/* Make secure copy of signed header */
    	shdr = shdr_alloc_and_copy(0, ta, ta_size);
    	if (!shdr) {
    		res = TEE_ERROR_SECURITY;
    		goto error_free_payload;
    	}
    
    //验签elf的签名合法性
    	/* Validate header signature */
    	FTMN_CALL_FUNC(res, &ftmn, FTMN_INCR0, shdr_verify_signature, shdr);
    	incr0_count++;
    	if (res != TEE_SUCCESS)
    		goto error_free_payload;
    
    	shdr_sz = SHDR_GET_SIZE(shdr);
    	if (!shdr_sz) {
    		res = TEE_ERROR_SECURITY;
    		goto error_free_payload;
    	}
    	offs = shdr_sz;
    
    	while (shdr->img_type == SHDR_SUBKEY) {
    		struct shdr_pub_key pub_key = { };
    
    		if (offs > ta_size) {
    			res = TEE_ERROR_SECURITY;
    			goto error_free_payload;
    		}
    
    		res = shdr_load_pub_key(shdr, offs, (const void *)ta,
    					ta_size, next_uuid_ptr, max_depth,
    					&pub_key);
    		if (res)
    			goto error_free_payload;
    
    		if (ADD_OVERFLOW(offs, shdr->img_size, &offs) ||
    		    ADD_OVERFLOW(offs, pub_key.name_size, &offs) ||
    		    offs > ta_size) {
    			res = TEE_ERROR_SECURITY;
    			goto error_free_payload;
    		}
    		max_depth = pub_key.max_depth;
    		memcpy(next_uuid, pub_key.next_uuid, sizeof(TEE_UUID));
    		next_uuid_ptr = next_uuid;
    
    		res = check_update_version(subkey_ver_db, pub_key.uuid,
    					   pub_key.version);
    		if (res) {
    			res = TEE_ERROR_SECURITY;
    			shdr_free_pub_key(&pub_key);
    			goto error_free_payload;
    		}
    
    		shdr_free(shdr);
    		shdr = shdr_alloc_and_copy(offs, ta, ta_size);
    		res = TEE_ERROR_SECURITY;
    		if (shdr) {
    			FTMN_CALL_FUNC(res, &ftmn, FTMN_INCR0,
    				       shdr_verify_signature2, &pub_key, shdr);
    			incr0_count++;
    		}
    		shdr_free_pub_key(&pub_key);
    		if (res)
    			goto error_free_payload;
    
    		shdr_sz = SHDR_GET_SIZE(shdr);
    		if (!shdr_sz) {
    			res = TEE_ERROR_SECURITY;
    			goto error_free_payload;
    		}
    		offs += shdr_sz;
    		if (offs > ta_size) {
    			res = TEE_ERROR_SECURITY;
    			goto error_free_payload;
    		}
    	}
    
    	if (shdr->img_type != SHDR_TA && shdr->img_type != SHDR_BOOTSTRAP_TA &&
    	    shdr->img_type != SHDR_ENCRYPTED_TA) {
    		res = TEE_ERROR_SECURITY;
    		goto error_free_payload;
    	}
    
    	/*
    	 * If we're verifying this TA using a subkey, make sure that
    	 * the UUID of the TA belongs to the namespace defined by the subkey.
    	 * The namespace is defined as in RFC4122, that is, valid UUID
    	 * is calculated as a V5 UUID SHA-512(subkey UUID, "name string").
    	 */
    	if (next_uuid_ptr) {
    		TEE_UUID check_uuid = { };
    
    		tee_uuid_from_octets(&check_uuid, next_uuid_ptr);
    		if (memcmp(&check_uuid, uuid, sizeof(*uuid))) {
    			res = TEE_ERROR_SECURITY;
    			goto error_free_payload;
    		}
    	}
    
    	/*
    	 * Initialize a hash context and run the algorithm over the signed
    	 * header (less the final file hash and its signature of course)
    	 */
    	res = crypto_hash_alloc_ctx(&hash_ctx,
    				    TEE_DIGEST_HASH_TO_ALGO(shdr->algo));
    	if (res != TEE_SUCCESS)
    		goto error_free_payload;
    	res = crypto_hash_init(hash_ctx);
    	if (res != TEE_SUCCESS)
    		goto error_free_hash;
    	res = crypto_hash_update(hash_ctx, (uint8_t *)shdr, sizeof(*shdr));
    	if (res != TEE_SUCCESS)
    		goto error_free_hash;
    
    	if (shdr->img_type == SHDR_BOOTSTRAP_TA ||
    	    shdr->img_type == SHDR_ENCRYPTED_TA) {
    		TEE_UUID bs_uuid = { };
    		size_t sz = shdr_sz;
    
    		if (ADD_OVERFLOW(sz, sizeof(*bs_hdr), &sz) || ta_size < sz) {
    			res = TEE_ERROR_SECURITY;
    			goto error_free_hash;
    		}
    
    		bs_hdr = malloc(sizeof(*bs_hdr));
    		if (!bs_hdr) {
    			res = TEE_ERROR_OUT_OF_MEMORY;
    			goto error_free_hash;
    		}
    
    		memcpy(bs_hdr, (uint8_t *)ta + offs, sizeof(*bs_hdr));
    
    		/*
    		 * There's a check later that the UUID embedded inside the
    		 * ELF is matching, but since we now have easy access to
    		 * the expected uuid of the TA we check it a bit earlier
    		 * here.
    		 */
    		tee_uuid_from_octets(&bs_uuid, bs_hdr->uuid);
    		if (memcmp(&bs_uuid, uuid, sizeof(TEE_UUID))) {
    			res = TEE_ERROR_SECURITY;
    			goto error_free_hash;
    		}
    
    		res = crypto_hash_update(hash_ctx, (uint8_t *)bs_hdr,
    					 sizeof(*bs_hdr));
    		if (res != TEE_SUCCESS)
    			goto error_free_hash;
    		offs += sizeof(*bs_hdr);
    		handle->bs_hdr = bs_hdr;
    	}
    
    	if (shdr->img_type == SHDR_ENCRYPTED_TA) {
    		struct shdr_encrypted_ta img_ehdr = { };
    		size_t sz = shdr_sz;
    		size_t ehdr_sz = 0;
    
    		if (ADD_OVERFLOW(sz, sizeof(struct shdr_bootstrap_ta), &sz) ||
    		    ADD_OVERFLOW(sz, sizeof(img_ehdr), &sz) ||
    		    ta_size < sz) {
    			res = TEE_ERROR_SECURITY;
    			goto error_free_hash;
    		}
    
    		memcpy(&img_ehdr, ((uint8_t *)ta + offs), sizeof(img_ehdr));
    		ehdr_sz = SHDR_ENC_GET_SIZE(&img_ehdr);
    		sz -= sizeof(img_ehdr);
    		if (!ehdr_sz || ADD_OVERFLOW(sz, ehdr_sz, &sz) ||
    		    ta_size < sz) {
    			res = TEE_ERROR_SECURITY;
    			goto error_free_hash;
    		}
    
    		/*
    		 * This is checked further down too, but we must sanity
    		 * check shdr->img_size before it's used below.
    		 */
    		if (ta_size != offs + ehdr_sz + shdr->img_size) {
    			res = TEE_ERROR_SECURITY;
    			goto error_free_hash;
    		}
    
    		ehdr = malloc(ehdr_sz);
    		if (!ehdr) {
    			res = TEE_ERROR_OUT_OF_MEMORY;
    			goto error_free_hash;
    		}
    
    		*ehdr = img_ehdr;
    		memcpy((uint8_t *)ehdr + sizeof(img_ehdr),
    		       (uint8_t *)ta + offs + sizeof(img_ehdr),
    		       ehdr_sz - sizeof(img_ehdr));
    
    		res = crypto_hash_update(hash_ctx, (uint8_t *)ehdr, ehdr_sz);
    		if (res != TEE_SUCCESS)
    			goto error_free_hash;
    
    		res = tee_ta_decrypt_init(&handle->enc_ctx, ehdr,
    					  shdr->img_size);
    		if (res != TEE_SUCCESS)
    			goto error_free_hash;
    
    		offs += ehdr_sz;
    		handle->ehdr = ehdr;
    	}
    
    	if (ta_size != offs + shdr->img_size) {
    		res = TEE_ERROR_SECURITY;
    		goto error_free_hash;
    	}
    
    	handle->nw_ta = ta;
    	handle->nw_ta_size = ta_size;
    	handle->offs = offs;
    	handle->hash_ctx = hash_ctx;
    	handle->shdr = shdr;
    	handle->mobj = mobj;
    	*h = (struct ts_store_handle *)handle;
    	FTMN_CALLEE_DONE_CHECK(&ftmn, FTMN_INCR1,
    			       FTMN_STEP_COUNT(incr0_count), TEE_SUCCESS);
    	return TEE_SUCCESS;
    
    error_free_hash:
    	crypto_hash_free_ctx(hash_ctx);
    error_free_payload:
    	thread_rpc_free_payload(mobj);
    error:
    	free(ehdr);
    	free(bs_hdr);
    	shdr_free(shdr);
    	free(handle);
    	FTMN_SET_CHECK_RES_NOT_ZERO(&ftmn, FTMN_INCR1, res);
    	FTMN_CALLEE_DONE_CHECK(&ftmn, FTMN_INCR1,
    			       FTMN_STEP_COUNT(incr0_count, 1), res);
    	return res;
    }

总结:

  1. 单实例多会话:多数 TA 是单实例模式,同一个 TA 只会加载一次,后续打开会话直接复用实例,减少加载开销。
  2. 按需加载:只有首次打开会话时才会触发加载,不调用不占用安全内存。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值