32. secure world对smc请求的处理------open session操作在OP-TEE中的实现

    历经一年多时间的系统整理合补充,《手机安全和可信应用开发指南:TrustZone与OP-TEE技术详解 》一书得以出版,书中详细介绍了TEE以及系统安全中的所有内容,全书按照从硬件到软件,从用户空间到内核空间的顺序对TEE技术详细阐述,读者可从用户空间到TEE内核一步一步了解系统安全的所有内容,同时书中也提供了相关的示例代码,读者可根据自身实际需求开发TA。目前该书已在天猫、京东、当当同步上线,链接如下(麻烦书友购书时能给予评论,多谢多谢)

京东购买地址

当当购买地址

天猫购买地址

非常感谢在此期间大家的支持以及各位友人的支持和帮助!!!。

为方便和及时的回复读者对书中或者TEE相关的问题的疑惑(每天必看一次),也为了大家能有一个统一的交流平台。我搭建了一个简单的论坛,网址如下:

https://www.huangtengxq.com/discuz/forum.php

关于您的疑问可在“相关技术讨论“”中发帖,我会逐一回复。也欢迎大家发帖,一起讨论TEE相关的一些有意思的feature。共同交流。同时该论坛中也会添加关于移动端虚拟化的相关技术的板块,欢迎各位共同交流学习

若觉得书中内容有错误的地方,欢迎大家指出,私信或者在博文中留言联系方式亦可发邮件至:[email protected],多谢各位了!!!!我会第一时间处理

  

session是CA调用TA的基础,如果CA没与TA之间没有建立session,那么CA就无法调用TA中的任何command。在libteec中通过执行TEEC_OpenSession函数来建议CA与特定TA之间的session的操作,该函数执行时会调用到OP-TEE驱动中的optee_open_session函数通知OP-TEE开始执行创建session的操作。在OP-TEE中的TEE端一次对动态TA完整的open session操作的流程如下图所示:

32. secure world对smc请求的处理------open session操作在OP-TEE中的实现_第1张图片

  OP-TEE支持动态TA和静态TA。即可以将TA镜像与OP-TEE的镜像编译在同一image中,只是静态TA镜像会存放在OP-TEE镜像的特定分区中。在OP-TEE启动的时候会被加载到属性为MEM_AREA_TA_RAM的安全内存中。而动态TA的方式则是将TA镜像文件保存到文件系统中,在打开session的时候再通过RPC请求加载到OP-TEE的安全内存中。open session在OP-TEE中的操作都是根据UUID值找到对应的TA镜像,然后读取TA image head部分的数据,并将相关数据保存到tee_ta_ctx结构体变量中,然后将填充好的tee_ta_ctx结构体变量保存到tee_ctxes链表中,以便后期CA执行invoke操作来调用TA中的command的时候可以通过查找tee_ctxes链表来获取对应的session,然后根据session的内容进入到TA中根据command ID执行特定的command操作。由于该部分代码量较大,故不对每一步的代码进行讲解,只对比较迷惑或者关键的地方进行介绍。详细内容可以结合上图和实际代码进行了解。

1. 静态TA的open session操作

  由于静态的TA是与OP-TEE OS镜像编译在一起,在OP-TEE的启动阶段该,静态TA镜像的内容会被加载OP-TEE的安全内存中,而且在启动过程中会调用verify_pseudo_tas_conformance函数对所有的静态TA镜像的内容进行检查。

  调用Open session操作后,OP-TEE首先会在已经被打开的session链表中查找是否有匹配,如果匹配则将该session的ID直接返回给REE侧,如果没有找到则会根据UUID去静态TA的段中进行查找,然后将找到的静态TA的相关信息填充到tee_ta_ctx结构体变量中,然后在添加到全局的tee_ctxes链表中。该函数内容如下:

 

TEE_Result tee_ta_init_pseudo_ta_session(const TEE_UUID *uuid,
			struct tee_ta_session *s)
{
	struct pseudo_ta_ctx *stc = NULL;
	struct tee_ta_ctx *ctx;
	const struct pseudo_ta_head *ta;

	DMSG("   Lookup for pseudo TA %pUl", (void *)uuid);

/* 获取静态TA的head的起始地址 */
	ta = &__start_ta_head_section;

/* 进入到loop循环,遍历整个段,根据UUID是否匹配来判定在静态TA的head段中是否有
相应的TA */
	while (true) {
		if (ta >= &__stop_ta_head_section)
			return TEE_ERROR_ITEM_NOT_FOUND;
		if (memcmp(&ta->uuid, uuid, sizeof(TEE_UUID)) == 0)
			break;
		ta++;
	}

	/* Load a new TA and create a session */
	DMSG("      Open %s", ta->name);
/* 分配存放pseudo_ta_ctx结构体变量的内存空间 */
	stc = calloc(1, sizeof(struct pseudo_ta_ctx));
	if (!stc)
		return TEE_ERROR_OUT_OF_MEMORY;
	ctx = &stc->ctx;

/* 填充数据,组合ctx变量,并将TA的operation的变量指针保存到ctx->ops中 */
	ctx->ref_count = 1;
	s->ctx = ctx;
	ctx->flags = ta->flags;
	stc->pseudo_ta = ta;
	ctx->uuid = ta->uuid;
	ctx->ops = &pseudo_ta_ops;
	TAILQ_INSERT_TAIL(&tee_ctxes, ctx, link);

	DMSG("      %s : %pUl", stc->pseudo_ta->name, (void *)&ctx->uuid);

	return TEE_SUCCESS;
}

 

2. 动态TA的open session操作

 

  动态的TA image会被保存在REE侧的文件系统中。CA在执行动态TA的open session操作的时候,OP-TEE会根据UUID值使用RPC机制借助tee_supplicant来完成将动态TA image加载到OP-TEE的内存中的操作,并获取加载到内存中的动态TA的相关信息,将这些信息填充到tee_ta_ctx结构体变量中,然后在添加到全局的tee_ctxes链表中。以便后续在CA端调用invoke操作直接根据session ID值在链表中找到该session.加载动态TA的过程可参考开篇的那张图。

2.1 RPC请求的触发

  在动态加载TA image的过程中是通过thread_rpc_cmd函数来触发RPC请求,借助tee_supplicant来完成TA image内容的读取和传输。该函数内容如下:

uint32_t thread_rpc_cmd(uint32_t cmd, size_t num_params,
			struct optee_msg_param *params)
{
	uint32_t rpc_args[THREAD_RPC_NUM_ARGS] = { OPTEE_SMC_RETURN_RPC_CMD };
	struct optee_msg_arg *arg;
	uint64_t carg;
	size_t n;
	struct optee_msg_param *arg_params;

	/*
	 * Break recursion in case plat_prng_add_jitter_entropy_norpc()
	 * sleeps on a mutex or unlocks a mutex with a sleeper (contended
	 * mutex).
	 */
	if (cmd != OPTEE_MSG_RPC_CMD_WAIT_QUEUE)
		plat_prng_add_jitter_entropy_norpc();

/* 获取需要通过RPC机制发送到REE侧的参数内容 */
	if (!get_rpc_arg(cmd, num_params, &arg, &carg, &arg_params))
		return TEE_ERROR_OUT_OF_MEMORY;

/* 拷贝操作 */
	memcpy(arg_params, params, sizeof(*params) * num_params);

/* 转换成64位,以便兼容64位系统 */
	reg_pair_from_64(carg, rpc_args + 1, rpc_args + 2);
/* 发送RPC请求,触发smc和suspend当前thread */
	thread_rpc(rpc_args);
	for (n = 0; n < num_params; n++) {
		switch (params[n].attr & OPTEE_MSG_ATTR_TYPE_MASK) {
		case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT:
		case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT:
		case OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT:
		case OPTEE_MSG_ATTR_TYPE_RMEM_INOUT:
		case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT:
		case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT:
			params[n] = arg_params[n];
			break;
		default:
			break;
		}
	}
	return arg->ret;
}

  RPC请求是如何发送出去的呢,发送RPC请求的时候做了哪些操作呢?这就得看thread_rpc函数做了哪些操作了,该函数是以汇编的方式实现的。

FUNC thread_rpc , :
/*
 * r0-r2 are used to pass parameters to normal world
 * r0-r5 are used to pass return vaule back from normal world
 *
 * note that r3 is used to pass "resume information", that is, which
 * thread it is that should resume.
 *
 * Since the this function is following AAPCS we need to preserve r4-r5
 * which are otherwise modified when returning back from normal world.
 */
UNWIND(	.fnstart)
	push	{r4-r5, lr}
UNWIND(	.save	{r4-r5, lr})
	push	{r0}
UNWIND(	.save	{r0})

	bl	thread_save_state //保存状态
	mov	r4, r0			/* Save original CPSR */

	/*
 	 * Switch to temporary stack and SVC mode. Save CPSR to resume into.
	 */
	bl	thread_get_tmp_sp	//获取tmp栈空间
	ldr	r5, [sp]		/* Get pointer to rv[] */
	cps	#CPSR_MODE_SVC		/* Change to SVC mode */
	mov	sp, r0			/* Switch to tmp stack */

	mov	r0, #THREAD_FLAGS_COPY_ARGS_ON_RETURN
	mov	r1, r4			/* CPSR to restore */
	ldr	r2, =.thread_rpc_return	//thred_rpc_return为当前线程resume回来之后的PC值
	bl	thread_state_suspend	//suspend当前线程
	mov	r4, r0			/* Supply thread index */
	ldr	r0, =TEESMC_OPTEED_RETURN_CALL_DONE
	ldm	r5, {r1-r3}		/* Load rv[] into r0-r2 */	
	smc	#0	//触发smc操作,切回到REE侧
	b	.	/* SMC should not return */

.thread_rpc_return:
	/*
	 * At this point has the stack pointer been restored to the value
	 * it had when thread_save_state() was called above.
	 *
	 * Jumps here from thread_resume above when RPC has returned. The
	 * IRQ and FIQ bits are restored to what they where when this
	 * function was originally entered.
	 */
	pop	{r12}			/* Get pointer to rv[] */
	stm	r12, {r0-r5}		/* Store r0-r5 into rv[] */
	pop	{r4-r5, pc}
UNWIND(	.fnend)
END_FUNC thread_rpc

  调用thread_rpc 函数会将调用的thread挂起,然后触发smc操作,切回到REE侧,让tee_supplicant来进行动态TA image的读取,然后再次通过RPC类型的smc请求将数据发送给OP-TEE。在一个完整的动态TA image的加载过程中对多次调用thread_rpc_cmd函数来发送RPC请求通过tee_supplican来从文件系统中获取不同的数据信息。

 

2.2 thread的resume

 

  发送RPC请求到REE侧的时候会将当前thread suspend。等OP-TEE接收到来自REE侧的请求处理结果后会将该thread重新resume。当在REE侧处理完请求之后,驱动会发送一个RPC类型的smc操作,该请求最终会调用thread_resume_from_rpc来进行处理。该函数内容如下:

static void thread_resume_from_rpc(struct thread_smc_args *args)
{
	size_t n = args->a3; /* thread id */	//获取需要被resume的thread ID	
	struct thread_core_local *l = thread_get_core_local();
	uint32_t rv = 0;

	assert(l->curr_thread == -1);

	lock_global();

/* 设定需要被唤醒的thread的状态变量 */
	if (n < CFG_NUM_THREADS &&
	    threads[n].state == THREAD_STATE_SUSPENDED &&
	    args->a7 == threads[n].hyp_clnt_id)
		threads[n].state = THREAD_STATE_ACTIVE;
	else
		rv = OPTEE_SMC_RETURN_ERESUME;

	unlock_global();

	if (rv) {
		args->a0 = rv;
		return;
	}

/* 指定当前thread的ID值 */
	l->curr_thread = n;

/* 判定需要被唤醒的线程是否属处于user态 */
	if (is_user_mode(&threads[n].regs))
		tee_ta_update_session_utime_resume();

/* 判定需要被唤醒的线程是否做了OP-TEE的userspace的内存映射 */
	if (threads[n].have_user_map)
		core_mmu_set_user_map(&threads[n].user_map);

	/*
	 * Return from RPC to request service of a foreign interrupt must not
	 * get parameters from non-secure world.
	 */
	if (threads[n].flags & THREAD_FLAGS_COPY_ARGS_ON_RETURN) {
		copy_a0_to_a5(&threads[n].regs, args);		//获取来自REE的回复数据
		threads[n].flags &= ~THREAD_FLAGS_COPY_ARGS_ON_RETURN;
	}

	thread_lazy_save_ns_vfp();
	thread_resume(&threads[n].regs);	//执行线程的resume操作
}

最终通过调用thread_resume函数来讲线程唤醒,该函数的内容如下:

FUNC thread_resume , :
UNWIND(	.fnstart)
UNWIND(	.cantunwind)
	add	r12, r0, #(13 * 4)	/* Restore registers r0-r12 later */

/* 切换到sys模式并保存相关寄存器 */
	cps	#CPSR_MODE_SYS
	ldm	r12!, {sp, lr}

/* 切换到svc模式并保存相关寄存器 */
	cps	#CPSR_MODE_SVC
	ldm	r12!, {r1, sp, lr}
	msr	spsr_fsxc, r1

/* 切换到SVC模式,并执行出栈操作 */
	cps	#CPSR_MODE_SVC
	ldm	r12, {r1, r2}
	push	{r1, r2}

	ldm	r0, {r0-r12}

	/* Restore CPSR and jump to the instruction to resume at */
	rfefd	sp!	//执行线程并返回
UNWIND(	.fnend)
END_FUNC thread_resume

  如果线程是在执行openssion通过调用thread_resume_from_rpc函数被挂起的,那么执行完thread_resume函数之后就会去执行thread_rpc_return段的代码执行出栈等操作后返回到thread_rpc_cmd函数继续执行,完成剩下的opensession操作。

 

总结

 

  如果TA image是被存放在文件系统中,即该TA为动态TA,那么在执行open session操作的时候需要通过RPC机制,借助tee_supplicant将TA image加载到OP-TEE中。在这个过程中会有多次的normal world和secure world的切换,并且有多次的RPC请求。




 

你可能感兴趣的:(OP-TEE,ARM,TrustZone技术)