optee patch --- Trusted OS欣赏

我准备改写它, 它在这
https://github.com/leesagacious/optee_os_patch-x64

本文将发 100 个patch,让你明白
1 : Linaro 那些“乌合之众”(?)的作者用意何在,
2 : 该patch对 Trusted OS 的影响
3 : 对 Rich OS (Linux Kernel )的影响
4 : 更好的实现方法
5 : 攻击方法 (专业Attack destroyer ?,努力尝试让TEE也挂?)
坚持努力 Attack op-tee !!

帮你彻底明白 OP-TEE Latest version changes

好,那就开始吧… 哈哈 ? ? (看完烦请点个赞哦, 不足之处还请下面留评论,thanks .定回复)

1:core: fix the reopen session condition for single instance TA
在这里插入图片描述
首先必须要明白的是
1 : 它的调用流程
2 : Trusted Application instance types

1:调用流程

/*
	该函数是它的直接调用者, 
	该函数名字如意思 就是想初始化一个tee_ta_session
	该tee_ta_session在本函数中是一个局部变量,最后本函数执行成功后会通过第4个
	参数传出.
	
	我们先看看它的caller传递给它哪些 resources, 
	下面A-0流程图显示该函数的"领导梯队"

    *err : 返回值
           看,上面的patch 重要性可见一斑了,如果 if 有失误(或者通过其他的方法destroy,就像patch fault一样),
           session别想打开了,后面的一切都别想了,
           
    *open_sessions : 一个全局的链表 tee_open_sessions ,
           链表的作用无需多说,session都往上挂接吧, 
           什么时候用到了,直接find_session()取出
           这里是一个可以改进的点,要找一个session,需要把链表全部查询一次,难道hash表做不到吗
    
    *uuid : 这个重要了,身份证号, 
            Each Trusted Application is identified by a Universally Unique Identifier
            好吧,以后会经常提到它的,这里先放放  

    **sess : out 参数,既有传出 又有传入 你家是开高速公路吧 ?  

   该函数的参数就决定了该函数能做什么, 就给你这么多资源,你自己玩吧 
   
   看到了吧,其实就是一个 UUID 在孤军深入, 其余的都是 no value !     
*/
static TEE_Result tee_ta_init_session(TEE_ErrorOrigin *err,
				    struct tee_ta_session_head *open_sessions,
				    const TEE_UUID *uuid,
				    struct tee_ta_session **sess)
{
    
    
    /*
        上来你就弄一个返回值 嘿嘿?
     */
    TEE_Result res;
	
    /*
	    好了,下面会调用 tee_ta_context_find() 查找tee_ta_ctx 了
	    查找的依据就是那个 UUID,关键的是 找到了 tee_ta_ctx 它做了什么
        就是本PATH的意思
	    tee_ta_ctx  : Context of a loaded TA, 这里和 Linux Kernel 中的 Task Context 大一不样
    */
    struct tee_ta_ctx *ctx;
    
    /*
    	分配内存空间->赋值->挂接全局链表, 
    	挂接链表方便以后find, remove,insert,地球人都知道了
    */
    struct tee_ta_session *s = calloc(1, sizeof(struct tee_ta_session));
    *err = TEE_ORIGIN_TEE;
    if (!s)
	return TEE_ERROR_OUT_OF_MEMORY;
		
    /*
       mask ? 却是一个 bool 
       client 可以随时取消打开一个session 
       关于这个 MaskCancellation 的问题有一大堆干货,这里不是重点
    */		
    s->cancel_mask = true;	
    /*
	    造了两把锁,
        TA_FLAG_MULTI_SESSION 会造成并发的现象? 显然不是,
	    只是多个session, 最终还是串行调用,就像 Uart 独占性一样
	*/
    condvar_init(&s->refc_cv);
    condvar_init(&s->lock_cv);
    /*
	    这个lock_thread 在 debug page fault 的时候 没少跟你打交道
	    在创建session的时候就初始化为 -1*/
    s->lock_thread = THREAD_ID_INVALID;
    /*
	    好了,重点来了 !
	    看 引用计数在这里 + 1 了
	    看本patch 478行,引用计数不是 0 的时候,就返回busy了
	    什么时机++ ,什么时机 -- ,为什么要++--
	    是理解该patch的一个重要的地方
    */
    s->ref_count = 1;
    /*
	    加锁了, 非重点 略过
    */
    mutex_lock(&tee_ta_mutex);
    /*
	    挂接到链表上去吧. 
    */
    TAILQ_INSERT_TAIL(open_sessions, s, link);
    /*
        根据UUID到 全局链表 tee_ctxes 链表上 查找 已经 load 的 TA 
        好,
        1 : 那么是在什么时机将 tee_ta_ctx 添加到全局链表 tee_ctxes 上的 ?
        2 : 显然这里如果找不到,我们上面的那个patch 将不会起作用了 ,?
             作者将继续 Look static TA 、user TA, 
             如果都找不到,那么就 销毁分配给的资源无奈 return 从哪儿来还会到哪里去了   

        好,下面就是本patch了 先回答上面的几个问题,在往下看吧
    */
    ctx = tee_ta_context_find(uuid);
    if (ctx) {
    
    
        res = tee_ta_init_session_with_context(ctx, s);
        if (res == TEE_SUCCESS || res != TEE_ERROR_ITEM_NOT_FOUND)
		    goto out;
    }
}								

这里只考虑运行在Rich Execution Environment 中的 Client
不考虑TA 通过内部客户端API 来担当另一个TA的 client 的场景
在这里插入图片描述

在这里插入图片描述

TEE_Result tee_ta_close_session (...)
{
    
    
	/*
		下面会为它们赋值
	*/
	struct tee_ta_session *sess;
	struct tee_ta_ctx *ctx;
	....
	/*
		从open_sessions 链表上查找 获取session
	*/
	sess = tee_ta_get_session((vaddr_t)csess, true, open_sessions);
	....
	/*
		获取session维护的 context TA
	*/
	ctx = sess->ctx;
	....
	/*close_session() 中将引用计数减少
		是否能顺利的减少引用计数,上面还判断了该TA是否busy
		什么样的TA是busy的,如何判断的,往下看吧
	*/
	ctx->ref_count--;
}

2 : Trusted Application instance types

1 : Multi Instance Trusted Application
2 : Single Instance Trusted Application

新增加了一个test_case ,创建了两个thread,每个thread都去open session
如下 :在这里插入图片描述

在这里插入图片描述

对与 single instance TA ,客户端打开的所有的 session 都定向到该instance of the TA,  
所有的session 共享该TA拥有的内存空间资源,

那么single instance TA 一定支持 多会话(TA_FLAG_MULTI_SESSION)了,看下面的macro 
如果不支持TA_FLAG_MULTI_SESSION,那么一个TA 实例只支持一个 session

所以,本patch 478行,判断该TA实例是否支持多会话,
如果 不支持多会话且上一个会话还没有结束,那么就返回 TEE_ERROR_BUSY 了
它使用一个变量来标志会话是否结束在以后的patch中改变了这样的判断手法
#define PTA_MANDATORY_FLAGS	(TA_FLAG_SINGLE_INSTANCE | \
				TA_FLAG_MULTI_SESSION | \
				TA_FLAG_INSTANCE_KEEP_ALIVE)

下面有一个mutex, 如果你按照下面的lock的角度来看 ,这里是有多个会话但是却是串行的体现. 其他的 thread 恼怒的痛苦等待吧,
在这里插入图片描述

看REE这边的情况

/*
	在里面下了一个deadloop
	哈哈, khungtaskd 看不下去出来说话了.
	请移步:
	https://blog.csdn.net/leesagacious/article/details/73694822
*/
INFO: task xtest:616 blocked for more than 120 seconds.
      Not tainted 4.5.0 #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
xtest           D 804ad090     0   616    613 0x00000001
[<804ad090>] (__schedule) from [<804ad618>] (schedule+0x4c/0xac)
[<804ad618>] (schedule) from [<804b0020>] (schedule_timeout+0x138/0x178)
[<804b0020>] (schedule_timeout) from [<804ae05c>] (wait_for_common+0x8c/0x13c)
[<804ae05c>] (wait_for_common) from [<803b6108>] (optee_handle_rpc+0x478/0x528)
[<803b6108>] (optee_handle_rpc) from [<803b54f8>] (optee_do_call_with_arg+0xd0/0xfc)
[<803b54f8>] (optee_do_call_with_arg) from [<803b5738>] (optee_open_session+0x118/0x1fc)
[<803b5738>] (optee_open_session) from [<803b36cc>] (tee_ioctl+0x898/0x103c)
[<803b36cc>] (tee_ioctl) from [<80101a30>] (do_vfs_ioctl+0x9c/0x8f8)
[<80101a30>] (do_vfs_ioctl) from [<801022c0>] (SyS_ioctl+0x34/0x5c)
[<801022c0>] (SyS_ioctl) from [<8000f640>] (ret_fast_syscall+0x0/0x3c)

2:core: deprecate TA_FLAG_USER_MODE

该patch 修正了识别 use_ta 与 pseudo_ta 的手法
在这里插入图片描述

TA_FLAG_USER_MODE : 表示TA是运行在TEE的用户空间

作者用下面的方式来判断是否是pseudo TA 上下文
在这里插入图片描述
好,下面就看看这样的判断手法的来龙去脉
在这里插入图片描述

/*
	@uuid : uuid
	看看该uuid的生命周期:
	
   将进行加载 位于文件系统中的TA
   最终是要把UUID通过RPC机制传递到REE,根据它来选择要加载的TA
res = ta_load(uuid, ....);
{
    
    
    ....
    res = ta_store->open(uuid, &ta_handle);  // ta_open()
    {
    
    	
       ....
       通过 tee-supplicant 来加载 TA image
       res = rpc_load(uuid, ....);
       {
    
    
            ....
            拷贝UUID的值 将会到 REE 侧加载与该UUID 相符的 TA image 
            到这里UUID化作春泥更护花,optee_msg_param会携带着UUID的信息继续前行
            
            这是当然的,你要做RPC切换,那么请填充 struct optee_msg_param,
            只是该重要的结构体在以后的patch中被换成了 thread_param了
            
            看到了吧,他们都是局部变量,Security 
            你要 Attack, 该optee_msg_param绝对是一个好的靶子 
            ↓
            tee_uuid_to_octets((void *)&params[0].u.value, uuid);
            {
    
     
                ....
	        将会发送RPC请求, 
	        RPC也许是一个Attack的点,它涉及到了Thread 的 suspend 和 resume 
	        ↓
               res = thread_rpc_cmd(OPTEE_MSG_RPC_CMD_LOAD_TA, 2, params);
               {
    
     
                   ....
		   UUID的值最终在TEE侧被保存到rpc_args 这个int类型的数组中
		   rpc_args 它是一个局部变量,很安全!thread_rpc(rpc_args);

@s : 被挂接到open_sessions链表上的刚分配好的内存tee_ta_session
     它在 ta_load()中属于享乐主义, 在该函数执行成功后的最后会执行下面一句话
     *ta_ctx = &utc->ctx; 
     用到了再说吧,这里不再赘述.
*/
TEE_Result tee_ta_init_user_ta_session(const TEE_UUID *uuid,
			struct tee_ta_session *s)
{
    
    
	/*
		加载动态TA的操作函数集合,
		你一定联想到了 linux drivier的file_operations 了吧,哈哈
		不过 它有一个优先级的概念,决定了挂接到链表上的顺序
		static struct user_ta_store_ops ops = {
    
    
			.description = "REE",
			.open = ta_open,
			.get_size = ta_get_size,
			.read = ta_read,
			.close = ta_close,
			.priority = 10, 
		};
	*/
	const struct user_ta_store_ops *store;
	/*
		你终于排在第二位了.
	*/
	TEE_Result res;

	/*
		遍历链表吧.
		遍历链表的目的就是想获取挂接在该链表上的加载TA的操作函数集合ops
		那么,是什么时候被挂接上去的呢 ?
		
		系统启动的时候被调用
		service_init(register_supplicant_user_ta);
		{
    
    
			return tee_ta_register_ta_store(&ops);
			{
    
    
				......
			}
		}
	*/
	SLIST_FOREACH(store, &uta_store_list, link) {
    
    
        /*
           开始加载动态TA.
           @uuid :  uuid
           @store:  加载该TA的工具集合
           @s->ctx: 传出参数,保存有该Thread的用户空间的信息
        */
        res = ta_load(uuid, store, &s->ctx);
       /*
          下面的code判断加载是否成功
        */
       if (res == TEE_ERROR_ITEM_NOT_FOUND)
           continue;
       if (res == TEE_SUCCESS)
           /*
             上面的那个是 user_ta_store_ops 用于加载动态TA
             这里是 user_ta_ops,他也是工具集,
             持有在OP-TEE内核空间中操作动态TA的接口函数集合
             这个重要了!!!  看下面的一个图 A-1,
           */
          s->ctx->ops = &user_ta_ops; 
       else
          DMSG("res=0x%x", res);
       return res;
      }
      return TEE_ERROR_ITEM_NOT_FOUND;
}			

A-1
在这里插入图片描述

static TEE_Result ta_load(const TEE_UUID *uuid,
                          const struct user_ta_store_ops *ta_store,
                          struct tee_ta_ctx **ta_ctx)
{
    
    
	...
	/*
		它保存了该Thread在optee-os 用户空间的信息
		操作动态TAThread会通过__thread_enter_user_mode() 从内核态切换到用户态,
		切换到用户态干什么 ?
		entry_open_session (user space)
		invoke TA Interface (a number of function)
		比如: TA_CreateEntryPoint  他是TA的constructor
		这里不是重点,详细的在后面的 patch 说
	*/
	struct user_ta_ctx *utc = NULL;
	...
	/*
		分配内存、赋值资源初始化它
	*/
	utc = calloc(1, sizeof(struct user_ta_ctx));
	if (!utc) {
    
    
		res = TEE_ERROR_OUT_OF_MEMORY;
		goto error_return;
	}
	...
	/*
		在这里为 TA运行上下文 设置操作动态TA的接口函数集合
		后面判断是动态Ta还是伪TA 是通过这里的ops来判断 ,看下图 A-2
		即完成了赋值操作,又作为判断依据
		static void set_ta_ctx_ops(struct tee_ta_ctx *ctx)
		{
    
    
			ctx->ops = &user_ta_ops;
		}
	*/
	set_ta_ctx_ops(&utc->ctx);
}			  		     

在这里插入图片描述
3: core: base pseudo_ta_register() on scatter array
在这里插入图片描述在这里插入图片描述

在系统启动期间, 会对pseudo_ta的合法性进行检查,
看241~249行,为了检查共6个(目前)pseudo_ta 的 uuid 是否相同,用了两个for循环,依次进行比对,这里难道不可以进行优化吗 ?

/**
	好,你要校验它们, 那么你要完成下面的准备工作
	
    1: pseudo_ta 怎么从里面拿出来、它们被放到了哪里 ?
    2:  优先级如何 ? 如何保证比它更紧急的任务被执行 ?
    3:  校验它们用意是什么 ? 或者说 pseudo_ta 产生的原因是什么?
    4: 如何Attack?
*/
static TEE_Result verify_pseudo_tas_conformance(void)
{
    
    
	/*
		这里涉及到链接脚本的内容了,内容在下图A-2
		
		第一个问题 : 怎么将pseudo_ta 放入到section中,然后怎么取出来
		放入:
		需要将特殊的数据(比如函数指针)放入到其他的section中,
		可使用section 如下面,具体的语法解析请参考官网
		
		#define __section(x)	__attribute__((section(x)))
		#define pseudo_ta_register(...) static const struct pseudo_ta_head __head \
			__used __section("ta_head_section") = {
    
     __VA_ARGS__ }
			
		看,都放到了ta_head_section 这个自定的section中了	
		该section中存储了所有pseudo_ta的内容 它们是op-tee镜像文件的一部分了(想一下user_ta在哪里?)
		
		<<庖丁解牛Linux内核分析>> P122	
		sections,是在ELF文件里用以装载内容数据的最小容器 

		这个 symbol的value : st_value 在链接的时候已经被赋值了,这样获取当然是可以的
		很简单,就是获取该段的地址,从里面取出pseudo_ta
		看该patch,在不同的platform上,通过patch提供的方式来获取到的结果是有所差异的.
		至少是在链接的时候虚拟地址就不同了
		该patch 在架构上没有创新之处,只是将分散的定义给统一起来,code复用方便罢了
	*/
	const struct pseudo_ta_head *start = &__start_ta_head_section;
	const struct pseudo_ta_head *end = &__stop_ta_head_section;
	const struct pseudo_ta_head *pta;
	/*
		这里面耐人寻味 ! 一个 pseudo_ta : 72byte.
		寻找下一个pseudo ta的时候 + 72byte  拿出来后进行比对uuid
	*/
	for (pta = start; pta < end; pta++) {
    
    
		const struct pseudo_ta_head *pta2;

		/* PTAs must all have a specific UUID */
		for (pta2 = pta + 1; pta2 < end; pta2++)
			if (!memcmp(&pta->uuid, &pta2->uuid, sizeof(TEE_UUID)))
				goto err;
		/*
			对pseudo ta进行校验吧, 
		*/
		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 用心良苦呀,作者大笔一挥,你就偏偏注定要在这里落脚了!
	*/
	panic("pta");
}

好,回答上面的问题:
1 : 在编译期间将pseudo_ta放到了自定义的节区中了, 在code中通过symbol就是该section的地址,就可以取出来,
pseudo_ta这样校验还能接受,user ta在启动阶段做这样的校验就是浪费时间了!
画蛇添足有正面意义吗? 估计只有蛇自己知道了 哈哈 ?
2: 看下面的图 pseudo_ta_ctx , 伪TA的上下文
在这里插入图片描述在这里插入图片描述

4: probe_max_it overwrite the value of GICD_ISENABLER
在这里插入图片描述
这个patch 是 2019年第一个patch, 要明白这个patch,必须要深入理解 GIC controller
你可以参考 :
https://blog.csdn.net/leesagacious/article/details/78679903
一)GIC controller

3: core: use struct thread_param for RPC
1 : 先看该函数的上半场
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/leesagacious/article/details/84259206