歷經一年多時間的系統整理合補充,《手機安全和可信應用開發指南:TrustZone與OP-TEE技術詳解 》一書得以出版,書中詳細介紹了TEE以及系統安全中的所有內容,全書按照從硬件到軟件,從用戶空間到內核空間的順序對TEE技術詳細闡述,讀者可從用戶空間到TEE內核一步一步瞭解系統安全的所有內容,同時書中也提供了相關的示例代碼,讀者可根據自身實際需求開發TA。目前該書已在天貓、京東、噹噹同步上線,鏈接如下(麻煩書友購書時能給予評論,多謝多謝):
非常感謝在此期間大家的支持以及各位友人的支持和幫助!!!。
爲方便和及時的回覆讀者對書中或者TEE相關的問題的疑惑(每天必看一次),也爲了大家能有一個統一的交流平臺。我搭建了一個簡單的論壇,網址如下:
https://www.huangtengxq.com/discuz/forum.php
關於您的疑問可在“相關技術討論“”中發帖,我會逐一回復。也歡迎大家發帖,一起討論TEE相關的一些有意思的feature。共同交流。同時該論壇中也會添加關於移動端虛擬化的相關技術的板塊,歡迎各位共同交流學習
若覺得書中內容有錯誤的地方,歡迎大家指出,私信或者在博文中留言聯繫方式亦可發郵件至:[email protected],多謝各位了!!!!我會第一時間處理
在《22. OP-TEE中TA與CA執行流程-------tee-supplicant(一)》一文中介紹了tee_supplicant主要作用,用來實現secure world端操作REE側文件系統,EMMC的rpmb分區,網絡socket操作,數據庫操作的需求。tee_supplicant與secure world之間的交互模式類似於生產者與消費者的方式進行配合來是實現上述需求。完成上述需求的整個過程包含三部分:1. 驅動獲取來自TEE側的請求。2.tee_supplicant從驅動中獲取TEE側的請求。3.驅動返回請求操作結果給TEE側。其整個過程的框圖如下:
當libtee調用驅動來與OP-TEE進行數據的交互的時候,最終會調用optee_do_call_with_arg函數完成完成smc的操作,而該函數中有一個loop循環,每次觸發smc操作之後會對從secure world中返回的參數res.a0進行判斷,判定當前從secure world返回的數據是要執行RPC操作還是直接返回到CA。如果是來自TEE的RPC請求,則會將請求存放到請求隊列req中。然後block住,直到tee_supplicant處理完請求並將req->c標記爲完成之後纔會進入下一個loop,重新出發smc操作,將處理結果返回給TEE。
1. 驅動獲取來自TEE側的請求
當libteec調用了需要做smc操作的請求之後,最終會調用到驅動的optee_do_call_with_arg函數,該函數會進入到死循環,第一條語句就會調用smc操作,進userspace的請求發送到secure world,待從secure world中返回之後。會對返回值進行判定。如果返回的res.a0參數是需要驅動做RPC操作,則該函數會調用到optee_handle_rpc操作。經過各種參數分析和函數調用之後,程序最後會調用optee_supp_thrd_req函數來將來自TEE的請求存放到tee_supplicant的請求隊列中。該函數的內容如下:
u32 optee_supp_thrd_req(struct tee_context *ctx, u32 func, size_t num_params,
struct tee_param *param)
{
struct optee *optee = tee_get_drvdata(ctx->teedev);
struct optee_supp *supp = &optee->supp;
struct optee_supp_req *req = kzalloc(sizeof(*req), GFP_KERNEL);
bool interruptable;
u32 ret;
if (!req)
return TEEC_ERROR_OUT_OF_MEMORY;
/* 初始化該請求消息的c成員並配置請求數據 */
init_completion(&req->c);
req->func = func;
req->num_params = num_params;
req->param = param;
/* Insert the request in the request list */
/* 將接受到的請求添加到驅動的TEE請求消息隊列中 */
mutex_lock(&supp->mutex);
list_add_tail(&req->link, &supp->reqs);
mutex_unlock(&supp->mutex);
/* Tell an eventual waiter there's a new request */
/* 將supp->reqs_c置位,通知tee_supplicant的receve操作,當前驅動中
有一個來自TEE的請求 */
complete(&supp->reqs_c);
/*
* Wait for supplicant to process and return result, once we've
* returned from wait_for_completion(&req->c) successfully we have
* exclusive access again.
*/
/* block在這裏,通過判定req->c是否被置位來判定當前請求是否被處理完畢,
而req->c的置位是有tee_supplicant的send調用來完成的,如果被置位,則進入到
while循環中進行返回值的設定並跳出while*/
while (wait_for_completion_interruptible(&req->c)) {
mutex_lock(&supp->mutex);
interruptable = !supp->ctx;
if (interruptable) {
/*
* There's no supplicant available and since the
* supp->mutex currently is held none can
* become available until the mutex released
* again.
*
* Interrupting an RPC to supplicant is only
* allowed as a way of slightly improving the user
* experience in case the supplicant hasn't been
* started yet. During normal operation the supplicant
* will serve all requests in a timely manner and
* interrupting then wouldn't make sense.
*/
interruptable = !req->busy;
if (!req->busy)
list_del(&req->link);
}
mutex_unlock(&supp->mutex);
if (interruptable) {
req->ret = TEEC_ERROR_COMMUNICATION;
break;
}
}
ret = req->ret;
kfree(req);
return ret;
}
當請求被處理完成之後,函數返回處理後的數據到optee_do_call_with_arg函數中,並進入optee_do_call_with_arg函數中while中的下一次循環,將處理結果返回給secure world.
2. tee_supplicant從驅動中獲取TEE側的請求
在tee_supplicant會調用read_request函數來從驅動的請求隊列中獲取當前存在的來自TEE的請求。該函數最終會調用到驅動中的optee_supp_recv函數。該函數的內容如下:
int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params,
struct tee_param *param)
{
struct tee_device *teedev = ctx->teedev;
struct optee *optee = tee_get_drvdata(teedev);
struct optee_supp *supp = &optee->supp;
struct optee_supp_req *req = NULL;
int id;
size_t num_meta;
int rc;
/* 對被用來存放TEE請求參數的數據的buffer進行檢查 */
rc = supp_check_recv_params(*num_params, param, &num_meta);
if (rc)
return rc;
/* 進入到loop循環中,從 驅動的請求消息隊列中獲取來自TEE中的請求,直到獲取之後纔會
跳出該loop*/
while (true) {
mutex_lock(&supp->mutex);
/* 嘗試從驅動的請求消息隊列中獲取來自TEE的一條請求 */
req = supp_pop_entry(supp, *num_params - num_meta, &id);
mutex_unlock(&supp->mutex);
/* 判定是否獲取到請求如果獲取到了則跳出該loop */
if (req) {
if (IS_ERR(req))
return PTR_ERR(req);
break;
}
/*
* If we didn't get a request we'll block in
* wait_for_completion() to avoid needless spinning.
*
* This is where supplicant will be hanging most of
* the time, let's make this interruptable so we
* can easily restart supplicant if needed.
*/
/* block在這裏,直到在optee_supp_thrd_req函數中發送了
complete(&supp->reqs_c)操作後才繼續往下執行 */
if (wait_for_completion_interruptible(&supp->reqs_c))
return -ERESTARTSYS;
}
/* 設定參數進行異步處理請求的條件 */
if (num_meta) {
/*
* tee-supplicant support meta parameters -> requsts can be
* processed asynchronously.
*/
param->attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT |
TEE_IOCTL_PARAM_ATTR_META;
param->u.value.a = id;
param->u.value.b = 0;
param->u.value.c = 0;
} else {
mutex_lock(&supp->mutex);
supp->req_id = id;
mutex_unlock(&supp->mutex);
}
/* 解析參數,設定tee_supplicant將要執行的具體(加載TA,操作文件系統,操作EMMC的
rpmb分區等)操作和相關參數 */
*func = req->func;
*num_params = req->num_params + num_meta;
memcpy(param + num_meta, req->param,
sizeof(struct tee_param) * req->num_params);
return 0;
}
從請求消息隊列中獲取到來自TEE的請求之後,返回到tee_supplicant中繼續執行。根據返回的func值和參數執行TEE要求在REE端需要的操作。
3. 驅動返回請求操作的結果給TEE側
當tee_supplicant執行完TEE請求的操作之後,會調用write_response函數來實現將數據返回給TEE。而write_response函數最終會調用到驅動的optee_supp_send函數。該函數主要是調用complete(&req->c);操作來完成對該請求的c成員的置位,告訴optee_supp_thrd_req函數執行下一步操作,返回到optee_do_call_with_arg函數中進入該函數中的下一輪loop中,調用smc操作將結果返回給TEE側。optee_supp_send函數的內容如下:
int optee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params,
struct tee_param *param)
{
struct tee_device *teedev = ctx->teedev;
struct optee *optee = tee_get_drvdata(teedev);
struct optee_supp *supp = &optee->supp;
struct optee_supp_req *req;
size_t n;
size_t num_meta;
mutex_lock(&supp->mutex);
/* 驅動中請求隊列的pop操作 */
req = supp_pop_req(supp, num_params, param, &num_meta);
mutex_unlock(&supp->mutex);
if (IS_ERR(req)) {
/* Something is wrong, let supplicant restart. */
return PTR_ERR(req);
}
/* Update out and in/out parameters */
/* 使用傳入的參數,更新請求的參數區域,將需要返回給TEE側的數據填入到對應的位置 */
for (n = 0; n < req->num_params; n++) {
struct tee_param *p = req->param + n;
switch (p->attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) {
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT:
p->u.value.a = param[n + num_meta].u.value.a;
p->u.value.b = param[n + num_meta].u.value.b;
p->u.value.c = param[n + num_meta].u.value.c;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT:
p->u.memref.size = param[n + num_meta].u.memref.size;
break;
default:
break;
}
}
req->ret = ret;
/* Let the requesting thread continue */
/* 通知optee_supp_thrd_req函數,一個來自TEE側的請求已經被處理完畢,
可以繼續往下執行 */
complete(&req->c);
return 0;
}
總結
從tee_supplicant處理來自TEE側的請求來看主要是有三個點。
第一是驅動在觸發smc操作之後會進入到loop循環中,根據secure world中的返回值來判定該返回時來自TEE的RPC請求還是最終處理結果,如果是RPC請求,也就是需要驅動或者tee_supplicant執行其他操作,驅動將RPC請求會保存到驅動的請求消息隊列中,然後block住等待請求處理結果。
第二是在tee_supplicant作爲一個常駐進程存在於REE中,它會不停的嘗試從驅動的請求消息隊列中獲取到來自TEE側的請求。如果請求消息隊列中並沒有請求則會block住,直到拿到了請求才返回。拿到請求之後會對請求進行解析,然後根據func執行具體的操作。
第三是在tee_supplicant處理完來自TEE的請求後,會調用send操作將處理結果存放到該消息隊列的參數區域,並使用complete函數通知驅動該請求已經被處理完畢。驅動block住的地方可以繼續往下執行,調用smc操作將結果返回給TEE側