我准备改写它, 它在这
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 *)¶ms[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 用户空间的信息
操作动态TA,Thread会通过__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 : 先看该函数的上半场