歷經一年多時間的系統整理合補充,《手機安全和可信應用開發指南:TrustZone與OP-TEE技術詳解 》一書得以出版,書中詳細介紹了TEE以及系統安全中的所有內容,全書按照從硬件到軟件,從用戶空間到內核空間的順序對TEE技術詳細闡述,讀者可從用戶空間到TEE內核一步一步瞭解系統安全的所有內容,同時書中也提供了相關的示例代碼,讀者可根據自身實際需求開發TA。目前該書已在天貓、京東、噹噹同步上線,鏈接如下(麻煩書友購書時能給予評論,多謝多謝):
非常感謝在此期間大家的支持以及各位友人的支持和幫助!!!。
爲方便和及時的回覆讀者對書中或者TEE相關的問題的疑惑(每天必看一次),也爲了大家能有一個統一的交流平臺。我搭建了一個簡單的論壇,網址如下:
https://www.huangtengxq.com/discuz/forum.php
關於您的疑問可在“相關技術討論“”中發帖,我會逐一回復。也歡迎大家發帖,一起討論TEE相關的一些有意思的feature。共同交流。同時該論壇中也會添加關於移動端虛擬化的相關技術的板塊,歡迎各位共同交流學習
驅動與secure world之間的數據交互是通過共享內存來完成的,在OP-TEE啓動的時候會將作爲共享內存的物理內存塊reserve出來,具體的可以查看OP-TEE啓動代碼中的core_init_mmu_map函數。在OP-TEE驅動初始化階段會將reserve出來作爲共享內存的物理內存配置成驅動的內存池,並且通知OP-TEE OS執行相同的動作。配置完成之後,secure world就能從共享內存中獲取到來自於REE端的數據。
1. 配置驅動與OP-TEE之間的共享內存
在驅動做probe操作時,會調用到optee_config_shm_memremap函數來完成OP-TEE驅動和OP-TEE之間共享內存的配置。該函數定義在linux/drivers/tee/optee/core.c文件中。其內容如下:
static struct tee_shm_pool *
optee_config_shm_memremap(optee_invoke_fn *invoke_fn, void **memremaped_shm)
{
union {
struct arm_smccc_res smccc;
struct optee_smc_get_shm_config_result result;
} res;
struct tee_shm_pool *pool;
unsigned long vaddr;
phys_addr_t paddr;
size_t size;
phys_addr_t begin;
phys_addr_t end;
void *va;
struct tee_shm_pool_mem_info priv_info;
struct tee_shm_pool_mem_info dmabuf_info;
/* 調用smc類操作,通知OP-TEE OS返回被reserve出來的共享內存的物理地址和大小 */
invoke_fn(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc);
if (res.result.status != OPTEE_SMC_RETURN_OK) {
pr_info("shm service not available\n");
return ERR_PTR(-ENOENT);
}
/* 判定是否提供secure world中的cache */
if (res.result.settings != OPTEE_SMC_SHM_CACHED) {
pr_err("only normal cached shared memory supported\n");
return ERR_PTR(-EINVAL);
}
/* 將對齊操作之後的物理內存塊的起始地址賦值給paddr,該塊內存的大小賦值給size */
begin = roundup(res.result.start, PAGE_SIZE);
end = rounddown(res.result.start + res.result.size, PAGE_SIZE);
paddr = begin;
size = end - begin;
/* 判定作爲共享內存的物理地址塊的大小是否大於兩個page大小,如果小於則報錯,因爲驅
動配置用於dma操作和普通共享內存的大小分別爲一個page大小*/
if (size < 2 * OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE) {
pr_err("too small shared memory area\n");
return ERR_PTR(-EINVAL);
}
// 將共享內存塊的物理地址映射到系統內存中,得到映射完成的虛擬地址,存放在va變量中
va = memremap(paddr, size, MEMREMAP_WB);
if (!va) {
pr_err("shared memory ioremap failed\n");
return ERR_PTR(-EINVAL);
}
vaddr = (unsigned long)va;
/* 配置驅動私有內存空間的虛擬地址的啓動地址,物理地址的起始地址以及大小,配置
dma緩存的虛擬起始地址和物理地址以及大小。dmabuf與privbuf兩個相鄰,分別爲一個
一個page的大小 */
priv_info.vaddr = vaddr;
priv_info.paddr = paddr;
priv_info.size = OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE;
dmabuf_info.vaddr = vaddr + OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE;
dmabuf_info.paddr = paddr + OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE;
dmabuf_info.size = size - OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE;
/* 將驅動的私有buffer和dma buffer添加到內存池中,以便驅動在使用本身的alloc函數
的時候能夠從私有共享內存和dma buffer中分配內存來使用 */
pool = tee_shm_pool_alloc_res_mem(&priv_info, &dmabuf_info);
if (IS_ERR(pool)) {
memunmap(va);
goto out;
}
/* 將驅動與OP-TEE的共享內存賦值給memremaped_shm變量執行的地址 */
*memremaped_shm = va;
out:
return pool; //返回共享內存池的結構體
}
在secure world中reserve出來的內存塊作爲驅動與sercure world之間的共享內存使用。在驅動端將會建立一個內存池,以便驅動在需要的使用通過調用驅動的alloc函數來完成共享內存的分配。而共享內存池的建立是通過調用tee_shm_pool_alloc_res_mem來實現的。其函數內容如下:
struct tee_shm_pool *
tee_shm_pool_alloc_res_mem(struct tee_shm_pool_mem_info *priv_info,
struct tee_shm_pool_mem_info *dmabuf_info)
{
struct tee_shm_pool *pool = NULL;
int ret;
/* 從內核空間的memory中分配一塊用於存放驅動內存池結構體變量的內存 */
pool = kzalloc(sizeof(*pool), GFP_KERNEL);
if (!pool) {
ret = -ENOMEM;
goto err;
}
/*
* Create the pool for driver private shared memory
*/
/* 調用pool相關函數完成內存池的創建,設定alloc時的分配算法,並將私有共享內存的
起始虛擬地址,起始物理地址以及大小信息保存到私有共享內存池中 */
ret = pool_res_mem_mgr_init(&pool->private_mgr, priv_info,
3 /* 8 byte aligned */);
if (ret)
goto err;
/*
* Create the pool for dma_buf shared memory
*/
/* 調用pool相關函數完成內存池的創建,設定alloc時的分配算法,並將dma共享內存的
起始虛擬地址,起始物理地址以及大小信息保存到dma的共享內存池中 */
ret = pool_res_mem_mgr_init(&pool->dma_buf_mgr, dmabuf_info,
PAGE_SHIFT);
if (ret)
goto err;
/* 設定銷燬共享內存池的接口函數 */
pool->destroy = pool_res_mem_destroy;
return pool; //返回內存池結構體
err:
if (ret == -ENOMEM)
pr_err("%s: can't allocate memory for res_mem shared memory pool\n", __func__);
if (pool && pool->private_mgr.private_data)
gen_pool_destroy(pool->private_mgr.private_data);
kfree(pool);
return ERR_PTR(ret);
}
2. 分配和設置OP-TEE驅動作爲被libteec和tee_supplicant使用的設備信息結構體變量
在OP-TEE驅動做probe操作時會分配和設置兩個tee_device結構體變量,分別用來表示被libteec和tee_supplicant使用的設備。分別通過調用tee_device_alloc(&optee_desc, NULL, pool, optee)和tee_device_alloc(&optee_supp_desc, NULL, pool, optee)來實現,主要是設置驅動作爲被libteec和tee_supplicant使用的設備的具體操作和表示該設備對應的名稱等信息,
當libteec調用文件操作函數執行打開,關閉等操作/dev/tee0設備文件的時,其最終將調用到optee_desc中具體的函數來實現對應操作。
當tee_supplicant調用文件操作函數執行打開,關閉等操作/dev/teepriv0設備文件的時,其最終將調用到optee_supp_desc中具體的函數來實現對應操作。
上述配置操作都是通過調用tee_device_all函數來實現的,該函數內容如下:
struct tee_device *tee_device_alloc(const struct tee_desc *teedesc,
struct device *dev,
struct tee_shm_pool *pool,
void *driver_data)
{
struct tee_device *teedev;
void *ret;
int rc;
int offs = 0;
/* 參數檢查 */
if (!teedesc || !teedesc->name || !teedesc->ops ||
!teedesc->ops->get_version || !teedesc->ops->open ||
!teedesc->ops->release || !pool)
return ERR_PTR(-EINVAL);
/* 從kerner space中分配用於存放tee_device變量的內存 */
teedev = kzalloc(sizeof(*teedev), GFP_KERNEL);
if (!teedev) {
ret = ERR_PTR(-ENOMEM);
goto err;
}
/* 判定當前分配的設備結構體是提供給libteec還是tee_supplicant,如果該設備時分配
給tee_supplicant,則將offs設置成16,offs將會在用於設置設備的id時被使用 */
if (teedesc->flags & TEE_DESC_PRIVILEGED)
offs = TEE_NUM_DEVICES / 2;
/* 查找dev_mask中的從Offs開始的第一個爲0的bit位,然後將該值作爲設備的id值 */
spin_lock(&driver_lock);
teedev->id = find_next_zero_bit(dev_mask, TEE_NUM_DEVICES, offs);
if (teedev->id < TEE_NUM_DEVICES)
set_bit(teedev->id, dev_mask);
spin_unlock(&driver_lock);
/* 判定設定的設備id是否超出最大數 */
if (teedev->id >= TEE_NUM_DEVICES) {
ret = ERR_PTR(-ENOMEM);
goto err;
}
/* 組合出設備名,對於libteec來說,設備名爲tee0。對於tee_supplicant來說,設備
名爲teepriv0 */
snprintf(teedev->name, sizeof(teedev->name), "tee%s%d",
teedesc->flags & TEE_DESC_PRIVILEGED ? "priv" : "",
teedev->id - offs);
/* 設定設備的class,tee_class在tee_init函數中被分配。設定執行設備release的操作函數
和dev.parent */
teedev->dev.class = tee_class;
teedev->dev.release = tee_release_device;
teedev->dev.parent = dev;
/* 將設備的主設備號和設備ID組合後轉化成dev_t類型 */
teedev->dev.devt = MKDEV(MAJOR(tee_devt), teedev->id);
/* 設置設備名,驅動被libteec使用時設備名爲tee0,驅動被tee_supplicant使用時設備名
爲teepriv0 */
rc = dev_set_name(&teedev->dev, "%s", teedev->name);
if (rc) {
ret = ERR_PTR(rc);
goto err_devt;
}
/* 設置驅動作爲字符設備的操作函數接口,即指定該驅動在執行open,close, ioctl的函數接口 */
cdev_init(&teedev->cdev, &tee_fops);
teedev->cdev.owner = teedesc->owner; //初始化字符設備的owner
teedev->cdev.kobj.parent = &teedev->dev.kobj; //初始化kobj.parent成員
/* 設置設備的私有數據 */
dev_set_drvdata(&teedev->dev, driver_data);
/* 初始化設備 */
device_initialize(&teedev->dev);
/* 1 as tee_device_unregister() does one final tee_device_put() */
teedev->num_users = 1; //標記該設備可以被使用
init_completion(&teedev->c_no_users);
mutex_init(&teedev->mutex);
idr_init(&teedev->idr);
/* 設定設備的desc成員,該成員包含設備最終執行具體操作的函數接口 */
teedev->desc = teedesc;
/* 設置設備的內存池,主要是驅動與secure world之間共享內存的私有共享內存和dma
操作共享內存 */
teedev->pool = pool;
return teedev;
err_devt:
unregister_chrdev_region(teedev->dev.devt, 1);
err:
pr_err("could not register %s driver\n",
teedesc->flags & TEE_DESC_PRIVILEGED ? "privileged" : "client");
if (teedev && teedev->id < TEE_NUM_DEVICES) {
spin_lock(&driver_lock);
clear_bit(teedev->id, dev_mask);
spin_unlock(&driver_lock);
}
kfree(teedev);
return ret;
}
3. 設備註冊
完成版本檢查,共享內存池配置,不同設備的配置之後就需要將這些配置好的設備註冊到系統中去。對於被liteec和tee_supplicant使用的設備分別通過調用tee_device_register(optee->teedev)和tee_device_register(optee->supp_teedev)來實現。其中optee->teedev和optee->supp_teedev就是在上一章中被配置好的分別被libteec和tee_supplicant使用的設備結構體。調用tee_device_register函數來實現將設備註冊到系統的目的,該函數內容如下:
int tee_device_register(struct tee_device *teedev)
{
int rc;
/* 判定設備是否已經被註冊過 */
if (teedev->flags & TEE_DEVICE_FLAG_REGISTERED) {
dev_err(&teedev->dev, "attempt to register twice\n");
return -EINVAL;
}
/* 註冊字符設備 */
rc = cdev_add(&teedev->cdev, teedev->dev.devt, 1);
if (rc) {
dev_err(&teedev->dev,
"unable to cdev_add() %s, major %d, minor %d, err=%d\n",
teedev->name, MAJOR(teedev->dev.devt),
MINOR(teedev->dev.devt), rc);
return rc;
}
/* 將設備添加到linux的設備模塊中,在該步中將會在/dev目錄下創設備驅動文件節點,即對於
被libteec使用的設備,在該步將創建/dev/tee0設備驅動文件。對於被tee_supplicant使用
的設備,在該步將創建/dev/teepriv0設備文件 */
rc = device_add(&teedev->dev);
if (rc) {
dev_err(&teedev->dev,
"unable to device_add() %s, major %d, minor %d, err=%d\n",
teedev->name, MAJOR(teedev->dev.devt),
MINOR(teedev->dev.devt), rc);
goto err_device_add;
}
/* 在/sys目錄下創建設備的屬性文件 */
rc = sysfs_create_group(&teedev->dev.kobj, &tee_dev_group);
if (rc) {
dev_err(&teedev->dev,
"failed to create sysfs attributes, err=%d\n", rc);
goto err_sysfs_create_group;
}
/* 設定該設備以及被註冊過 */
teedev->flags |= TEE_DEVICE_FLAG_REGISTERED;
return 0;
err_sysfs_create_group:
device_del(&teedev->dev);
err_device_add:
cdev_del(&teedev->cdev);
return rc;
}
4. 兩個隊列的建立
在OP-TEE驅動提供兩個設備,分別被libteec使用的/dev/tee0和被tee_supplicant使用的/dev/teepriv0。爲確保normal world與secure world之間數據交互便利和能在normal world端進行異步處理。OP-TEE驅動在掛載的時候會建立兩個類似於消息隊列的隊列,用於保存normal world的請求數據以及secure world請求。optee_wait_queue_init用於初始化/dev/tee0設備使用的隊列,optee_supp_init用於初始化/dev/teepriv0設備使用的隊列。其代碼分別如下:
void optee_wait_queue_init(struct optee_wait_queue *priv)
{
mutex_init(&priv->mu);
INIT_LIST_HEAD(&priv->db);
}
void optee_supp_init(struct optee_supp *supp)
{
memset(supp, 0, sizeof(*supp));
mutex_init(&supp->mutex);
init_completion(&supp->reqs_c);
idr_init(&supp->idr);
INIT_LIST_HEAD(&supp->reqs);
supp->req_id = -1;
}
5. 通知OP-TEE使能一些共享內存的cache
當一切做完之後,最終就剩下通知OP-TEE使能共享內存的cache了,在驅動掛載過程中調用optee_enable_shm_cache函數來實現,該函數內容如下:
void optee_enable_shm_cache(struct optee *optee)
{
struct optee_call_waiter w;
/* We need to retry until secure world isn't busy. */
/* 確定secure world是否ready */
optee_cq_wait_init(&optee->call_queue, &w);
/* 進入到loop循環,通知secure world執行相應操作,直到返回OK後跳出 */
while (true) {
struct arm_smccc_res res;
/* 調用smc操作,通知secure world執行使能共享內存cache的操作 */
optee->invoke_fn(OPTEE_SMC_ENABLE_SHM_CACHE, 0, 0, 0, 0, 0, 0,
0, &res);
if (res.a0 == OPTEE_SMC_RETURN_OK)
break;
optee_cq_wait_for_completion(&optee->call_queue, &w);
}
optee_cq_wait_final(&optee->call_queue, &w);
}
6. fast smc與std smc
在OP-TEE驅動的掛載過程中會使用fast smc的方式從OP-TEE中獲取到相關數據,例如從secure world中獲取reserve的共享內存信息時就是通過調用如下函數來實現的:
invoke_fn(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc);
在支持smc操作的32位系統中該函數等價於
arm_smccc_smc(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc);
而OPTEE_SMC_ENABLE_SHM_CACHE的定義如下:
#define OPTEE_SMC_FUNCID_GET_SHM_CONFIG 7
#define OPTEE_SMC_GET_SHM_CONFIG OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_GET_SHM_CONFIG)
完全展開之後OPTEE_SMC_GET_SHM_CONFIG 宏的值的各個bit中的數值如下如所示:
在執行smc操作時,cortex會解讀第一個參數中的相關位來決定進入到monitor模式後的相關操作,在ARM官方定義了第一個參數(a0)的定義如下
當bit31爲1時,則表示進入monitor模式後會執行fast smc的中斷處理函數,而在不帶ATF的ARCH32中,monitor的中斷向量表如下:
FUNC thread_vector_table , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
b vector_std_smc_entry
b vector_fast_smc_entry
b vector_cpu_on_entry
b vector_cpu_off_entry
b vector_cpu_resume_entry
b vector_cpu_suspend_entry
b vector_fiq_entry
b vector_system_off_entry
b vector_system_reset_entry
UNWIND( .fnend)
END_FUNC thread_vector_table
由此可以見,驅動在掛載的時候請求共享內存配置數據的請求將會被vector_fast_smc_entry處理。而當請求來自於CA的請求時,驅動會將第一個參數的bi31設置成0,也就是CA的請求會被vector_std_smc_entry進行處理
總結
OP-TEE的驅動的掛載過程來看,OP-TEE驅動會分別針對libteec和tee_supplicant建立不同的設備/dev/tee0和/dev/teepriv0。並且爲兩個設備中的des執行各自獨有的operation,並建立類似消息隊列來存放normal world與secure world之間的請求,這樣libteec和tee_supplicant使用OP-TEE驅動的時候就能做到相對的獨立。secure world與OP-TEE驅動之間使用共享內存進行數據交互。用於作爲共享內存的物理內存塊在OP-TEE啓動的時做MMU初始化時需要被reserve出來,在OP-TEE掛載過程中需要將該內存塊映射到系統內存中。