OP-TEE中的线程管理(四)

上面我们知道了IPC的相关东西,并回忆了系统调用的过程。
下面来看看这个IPC具体是怎么实现的。

3 IPC的实现

OP-TEE的IPC机制是通过系统调用陷入到内核中来实现的。调用其他TA的操作有专门的接口,而访问安全驱动和OP-TEE的服务则是通过在内核态中调用服务提供的内核级接口来实现的。

线程–》内核态–》服务/安全驱动给内核的接口

3.1 TA调用其他TA的实现

一个TA调用其他TA时,OP-TEE通过建立两者间的会话,并调用命令来实现。(这不是和TA CA通信一个样儿,当然还是有细微的差别)

GP规范定义了如表17-1中的三个接口,这些接口可在OP-TEE的用户空间被调用。

在这里插入图片描述
当一个TA需要调用其他的TA时,首先需要使用TEE_OpenTASession创建两个TA之间的会话,再使用TEE_InvokeTACommand调用到已经建立的会话的TA中的具体操作,待不再需要调用其他TA时,则调用TEE_InvokeTACommand函数关闭会话来断开两个TA间的联系。

下面看看这三个的实现

1. TEE_OpenTASession的实现

TEE_OpenTASession的实现与CA中创建与TA的会话的过程大致相同,但TEE_OpenTASession是通过系统调用的方式来触发OP-TEE分配线程并创建会话,而CA则是通过触发安全监控模式调用(smc)来让OP-TEE分配线程并创建会话。(都是TEE OS一手操办)

TEE_OpenTASession操作的整体流程如图17-1所示。
在这里插入图片描述
(感叹老师这个逻辑的清晰,看流程图,关键你就看判断就行。)

函数执行到tee_ta_open_session后,其操作与在CA创建会话的操作完全一致,syscall_open_ta_session函数的说明如下:

        TEE_Result syscall_open_ta_session(const TEE_UUID *dest,
                    unsigned long cancel_req_to,
                    struct utee_params *usr_param, uint32_t *ta_sess,
                    uint32_t *ret_orig)
        {
            TEE_Result res;
            uint32_t ret_o = TEE_ORIGIN_TEE;
            struct tee_ta_session *s = NULL;
            struct tee_ta_session *sess;
            struct mobj *mobj_param = NULL;
            TEE_UUID *uuid = malloc(sizeof(TEE_UUID));
            struct tee_ta_param *param = malloc(sizeof(struct tee_ta_param));
            TEE_Identity *clnt_id = malloc(sizeof(TEE_Identity));
            void *tmp_buf_va[TEE_NUM_PARAMS];
            struct user_ta_ctx *utc;
            /* 参数合法性检查 */
            if (uuid == NULL || param == NULL || clnt_id == NULL) {
                res = TEE_ERROR_OUT_OF_MEMORY;
                              goto out_free_only;
              }
              /* 清空分配的param变量中的数据 */
              memset(param, 0, sizeof(struct tee_ta_param));
              /* 获取当前TA的会话信息 */
              res = tee_ta_get_current_session(&sess);
              if (res ! = TEE_SUCCESS)
                  goto out_free_only;
              utc = to_user_ta_ctx(sess->ctx);
              /* 将用户空间传递的UUID值复制到内核空间中 */
              res = tee_svc_copy_from_user(uuid, dest, sizeof(TEE_UUID));
              if (res ! = TEE_SUCCESS)
                  goto function_exit;
              /* 设定login方式并设定clnt_id->uuid的值,即让当前TA认为是client端*/
              clnt_id->login = TEE_LOGIN_TRUSTED_APP;
              memcpy(&clnt_id->uuid, &sess->ctx->uuid, sizeof(TEE_UUID));
              /* 复制用户空间传递的参数数据到内核空间 */
              res = tee_svc_copy_param(sess, NULL, usr_param, param, tmp_buf_va,
                          &mobj_param);
              if (res ! = TEE_SUCCESS)
                  goto function_exit;
              /* 执行创建会话操作*/
              res = tee_ta_open_session(&ret_o, &s, &utc->open_sessions, uuid,
                          clnt_id, cancel_req_to, param);
              if (res ! = TEE_SUCCESS)
                  goto function_exit;
              /* 更新param的内容 */
              res = tee_svc_update_out_param(sess, s, param, tmp_buf_va, usr_param);
          function_exit:
              if (mobj_param) {
                  mutex_lock(&tee_ta_mutex);
                  mobj_free(mobj_param);
                  mutex_unlock(&tee_ta_mutex);
              }
              if (res == TEE_SUCCESS)
                  tee_svc_copy_kaddr_to_uref(ta_sess, s); //将获得的会话的ID值复制到用户空间
              //复制执行函数的返回值到用户空间
              tee_svc_copy_to_user(ret_orig, &ret_o, sizeof(ret_o));
          out_free_only:
              free(param);
              free(uuid);
              free(clnt_id);
              return res;
          }

关于tee_ta_open_session函数的执行过程可参阅本书第13章。

2. TEE_InvokeTACommand的实现

调用TEE_InvokeTACommands时带入命令ID的值就能调用TA中具体的命令,其过程与CA的命令调用操作几乎一致,该接口的执行流程如图17-2所示。

在这里插入图片描述
Syscall_invoke_ta_command的内容和说明如下:

    TEE_Result syscall_invoke_ta_command(unsigned long ta_sess,
                unsigned long cancel_req_to, unsigned long cmd_id,
                struct utee_params *usr_param, uint32_t *ret_orig)
    {
        TEE_Result res;
        TEE_Result res2;
        uint32_t ret_o = TEE_ORIGIN_TEE;
        struct tee_ta_param param = { 0 };
        TEE_Identity clnt_id;
        struct tee_ta_session *sess;
        struct tee_ta_session *called_sess;
        struct mobj *mobj_param = NULL;
        void *tmp_buf_va[TEE_NUM_PARAMS];
        struct user_ta_ctx *utc;
        /* 获取当前的TA的session信息 */
        res = tee_ta_get_current_session(&sess);
        if (res ! = TEE_SUCCESS)
            return res;
        utc = to_user_ta_ctx(sess->ctx);
        /* 根据session ID从保存已经open的session链表中找到对应的session */
        called_sess = tee_ta_get_session(
                    (vaddr_t)tee_svc_uref_to_kaddr(ta_sess), true,
                    &utc->open_sessions);
        if (! called_sess)
            return TEE_ERROR_BAD_PARAMETERS;
        /* 设定clnt_id的内容,将调用者作为client端处理 */
        clnt_id.login = TEE_LOGIN_TRUSTED_APP;
        memcpy(&clnt_id.uuid, &sess->ctx->uuid, sizeof(TEE_UUID));
        /* 复制从用户空间传入的参数 */
        res = tee_svc_copy_param(sess, called_sess, usr_param, &param,
                            tmp_buf_va, &mobj_param);
                if (res ! = TEE_SUCCESS)
                    goto function_exit;
                /* 开始调用找到的session中的invoke command,根据command ID执行指定的操作 */
                res = tee_ta_invoke_command(&ret_o, called_sess, &clnt_id,
                                cancel_req_to, cmd_id, &param);
                /* 更新执行结果到输出参数 */
                res2 = tee_svc_update_out_param(sess, called_sess, &param, tmp_buf_va,
                                usr_param);
                if (res2 ! = TEE_SUCCESS) {

                    ret_o = TEE_ORIGIN_TEE;
                    res = res2;
                }

            function_exit:
                tee_ta_put_session(called_sess);
                if (mobj_param) {
                    mutex_lock(&tee_ta_mutex);
                    mobj_free(mobj_param);
                    mutex_unlock(&tee_ta_mutex);
                }
                if (ret_orig)
                    tee_svc_copy_to_user(ret_orig, &ret_o, sizeof(ret_o));
                return res;
            }

关于tee_ta_invoke_command函数的执行过程可参阅第13章。

3. TEE_CloseTASession的实现

TEE_CloseTASession接口用于断开TA与其他TA之间的连接,其过程与CA的关闭会话操作几乎一致,该接口的执行流程如图17-3所示。
在这里插入图片描述
调用TEE_CloseTASession接口时会产生系统调用,系统会执行关闭会话的操作,syscall_close_ta_session函数的内容和说明如下:

        TEE_Result syscall_close_ta_session(unsigned long ta_sess)
        {
            TEE_Result res;
            struct tee_ta_session *sess;
            TEE_Identity clnt_id;
            struct tee_ta_session *s = tee_svc_uref_to_kaddr(ta_sess);
            struct user_ta_ctx *utc;
            /* 获取当前TA的session信息 */
            res = tee_ta_get_current_session(&sess);
            if (res ! = TEE_SUCCESS)
                return res;
            utc = to_user_ta_ctx(sess->ctx);
            /* 设定clnt_id信息 */
            clnt_id.login = TEE_LOGIN_TRUSTED_APP;
            memcpy(&clnt_id.uuid, &sess->ctx->uuid, sizeof(TEE_UUID));
            /* 将需要被关闭的session从保存已经Open的session链表中移除 */
            return tee_ta_close_session(s, &utc->open_sessions, &clnt_id);
        }

3.2 TA调用系统服务和安全驱动的实现

动态TA实现具体功能时需要调用到安全驱动或系统底层的资源。例如密码学操作、加载TA镜像文件操作、对SE模块的操作等。

这些资源提供的接口都处于OP-TEE的内核空间,当用户空间的TA需要使用这些资源来实现具体功能时,则需要让TA的调用操作通过系统调用的方式进入到内核空间,然后再调用特定的接口

1. OP-TEE中服务和安全驱动的构成框架

OP-TEE使用系统服务的方式统一管理各功能模块,安全驱动的操作接口会接入到系统服务中,系统服务是在OP-TEE启动过程中执行initcall段中的内容时被启动,service_init的启动等级设置为1,而driver_init的启动等级设置成3。

故在OP-TEE的启动过程中,首先会启动使用service_init宏定义的功能函数,再初始化安全驱动。

在这里插入图片描述
OP-TEE中的系统服务提供了类似框架层的功能,安全驱动初始化时会将驱动的操作接口注册到对应的系统服务。

TA可使用的只是各系统服务提供的接口,如果系统服务并不需要给上层TA使用,则不会暴露对应的接口给TA。

当前的OP-TEE中提供了如下三个重要的系统服务:

□ 密码学操作的系统服务;

□ 对SE功能模块进行操作的系统服务;

□ 提供加载TA镜像操作的系统服务;

2. TA对系统服务接口的调用实现

动态TA通过系统调用的方式进入到内核态,然后在内核态调用各系统服务提供的接口。

系统服务为OP-TEE用户态程序提供的接口定义在类似于tee_api_xxx.c的文件中,这些文件根据不同的功能模块定义了用户空间需要使用的密码学操作接口、SE操作接口等。这些接口的调用过程大致相同,如图17-5所示。

在这里插入图片描述

用户态的TA通过系统调用陷入OP-TEE内核空间,然后在对应的系统调用中使用系统服务提供的接口或变量来完成对安全驱动或其他资源的操作。

因为在安全,我们知道TEE侧一个重要的功能就是提供加密方面的。

3.3 TA对密码学系统服务的调用实现

TA需要实现计算摘要、产生随机数、加解密、签名验签等操作时就会调用到密码学系统服务提供的接口。

OP-TEE的内核空间中有一个变量——crypto_ops,该变量中保存了各种密码学算法的调用接口,其内容如下:

        const struct crypto_ops crypto_ops = {
            .name = "LibTomCrypt provider",      //该系统服务的名字
            //crypto service的初始化函数,在启动过程中将会执行crypto_ops.init指定的函数
            .init = tee_ltc_init,
        /* hash类算法的接口,用于计算摘要 */
          #if defined(_CFG_CRYPTO_WITH_HASH)
            .hash = {
                .get_ctx_size = hash_get_ctx_size,
                .init = hash_init,
                .update = hash_update,
                .final = hash_final,
            },
        #endif
        /* 对称加解密算法的接口用于对称加解密 */
        #if defined(_CFG_CRYPTO_WITH_CIPHER)
            .cipher = {
                .final = cipher_final,
                .get_block_size = cipher_get_block_size,
                .get_ctx_size = cipher_get_ctx_size,
                .init = cipher_init,
                .update = cipher_update,
            },
        #endif
        /* MAC类算法接口 */
        #if defined(_CFG_CRYPTO_WITH_MAC)
            .mac = {
                .get_ctx_size = mac_get_ctx_size,
                .init = mac_init,
                .update = mac_update,
                .final = mac_final,
            },
        #endif
        /* 对称验证加解密算法接口 */
        #if defined(_CFG_CRYPTO_WITH_AUTHENC)
            .authenc = {
                .dec_final = authenc_dec_final,
                .enc_final = authenc_enc_final,
                .final = authenc_final,
                .get_ctx_size = authenc_get_ctx_size,
                .init = authenc_init,
                .update_aad = authenc_update_aad,
                .update_payload = authenc_update_payload,
            },
        #endif
        /* 非对称算法(RSA)加解密,签名验签操作接口 */
        #if defined(_CFG_CRYPTO_WITH_ACIPHER)
        .acipher = {
    #if defined(CFG_CRYPTO_RSA)
            .alloc_rsa_keypair = alloc_rsa_keypair,
            .alloc_rsa_public_key = alloc_rsa_public_key,
            .free_rsa_public_key = free_rsa_public_key,
            .gen_rsa_key = gen_rsa_key,
            .rsaes_decrypt = rsaes_decrypt,
            .rsaes_encrypt = rsaes_encrypt,
            .rsanopad_decrypt = rsanopad_decrypt,
            .rsanopad_encrypt = rsanopad_encrypt,
            .rsassa_sign = rsassa_sign,
            .rsassa_verify = rsassa_verify,
    #endif
    /* 生成key的接口 */
    #if defined(CFG_CRYPTO_DH)
            .alloc_dh_keypair = alloc_dh_keypair,
            .gen_dh_key = gen_dh_key,
            .dh_shared_secret = do_dh_shared_secret,
    #endif
    /* DSA算法接口 */
    #if defined(CFG_CRYPTO_DSA)
            .alloc_dsa_keypair = alloc_dsa_keypair,
            .alloc_dsa_public_key = alloc_dsa_public_key,
            .gen_dsa_key = gen_dsa_key,
            .dsa_sign = dsa_sign,
            .dsa_verify = dsa_verify,
    #endif
    /* ECC算法接口 */
    #if defined(CFG_CRYPTO_ECC)
            /* ECDSA and ECDH */
            .alloc_ecc_keypair = alloc_ecc_keypair,
            .alloc_ecc_public_key = alloc_ecc_public_key,
            .gen_ecc_key = gen_ecc_key,
            .free_ecc_public_key = free_ecc_public_key,

            /* ECDSA only */
            .ecc_sign = ecc_sign,
            .ecc_verify = ecc_verify,
            /* ECDH only */
            .ecc_shared_secret = do_ecc_shared_secret,
    #endif
        },
    /* 大整数操作接口 */
        .bignum = {
            .allocate = bn_allocate,
            .num_bytes = num_bytes,
            .num_bits = num_bits,
            .compare = compare,
            .bn2bin = bn2bin,
            .bin2bn = bin2bn,
            .copy = copy,
            .free = bn_free,
            .clear = bn_clear
              },
          #endif /* _CFG_CRYPTO_WITH_ACIPHER */
          /* 随机系列算法接口 */
              .prng = {
                  .add_entropy = prng_add_entropy,
                  .read = prng_read,
              }
          };

1. crypto service的初始化

OP-TEE在启动时会调用crypto_ops.init指定的函数初始化整个密码学系统服务,即调用tee_ltc_init函数来初始化密码学系统服务,该函数将各种密码学算法的操作接口都注册到特定的变量中,这些变量与对应算法的关系如表17-2所示。(一个个服务组成了大的服务,服务就是对多个相同目的驱动的一个整合吧我觉得,C语言通过结构体去实现面向对象的逻辑)

在这里插入图片描述
启动密码学系统服务时,会调用tee_ltc_reg_algs函数将对应算法的操作接口注册到相关变量中,该函数内容如下:

        static void tee_ltc_reg_algs(void)
        {
        #if defined(CFG_CRYPTO_AES)
            register_cipher(&aes_desc);    //注册AES算法的操作接口到cipher_descriptor中
        #endif
        #if defined(CFG_CRYPTO_DES)
            register_cipher(&des_desc);    //注册DES算法的操作接口到cipher_descriptor中
            register_cipher(&des3_desc);   //注册DES3算法的操作接口到cipher_descriptor中
        #endif
        #if defined(CFG_CRYPTO_MD5)
            register_hash(&md5_desc);      //注册MD5算法的操作接口到hash_descriptor中
        #endif
        #if defined(CFG_CRYPTO_SHA1)
            register_hash(&sha1_desc);     //注册SHA1算法的操作接口到hash_descriptor中
        #endif
        #if defined(CFG_CRYPTO_SHA224)
            register_hash(&sha224_desc);   //注册SHA224算法的操作接口到hash_descriptor中
        #endif
        #if defined(CFG_CRYPTO_SHA256)
            register_hash(&sha256_desc);   //注册SHA256算法的操作接口到hash_descriptor中
        #endif
        #if defined(CFG_CRYPTO_SHA384)
            register_hash(&sha384_desc);   //注册SHA384算法的操作接口到hash_descriptor中
        #endif
        #if defined(CFG_CRYPTO_SHA512)
            register_hash(&sha512_desc);   //注册SHA512算法的操作接口到hash_descriptor中
        #endif
        //注册prng算法的操作接口到prng_descriptor中
        #if defined(CFG_WITH_SOFTWARE_PRNG)
        #if defined(_CFG_CRYPTO_WITH_FORTUNA_PRNG)
            register_prng(&fortuna_desc);
        #else
            register_prng(&rc4_desc);
        #endif
        #else
            register_prng(&prng_mpa_desc);
        #endif
        }

注册过程就是将具体密码学算法的operation变量保存到对应的数组变量元素中。密码学系统服务初始化完成后,内核空间通过调用crypto_ops.xxx.xxx的方式可调用到各种密码学算法的具体实现。

2. TA调用具体算法的实现

调用crypto_ops中的接口时,会根据需要被调用密码学算法的名称从数组变量中找到对应的元素,然后使用元素中保存的算法操作接口来完成密码学操作。

如果芯片集成了硬件加解密引擎,加密算法的实现,则可使用硬件cipher驱动提供的接口来完成(将这个驱动注册进服务,把接口加到对应的结构体里面。)。本节以调用SHA1算法为例介绍其实现过程。

在这里插入图片描述
(图17-6 TEE_DigestUpdate操作的实现流程)

下面再来看看SE。

3.4 对SE功能模块进行操作的系统服务

在OP-TEE内核空间调用类似tee_se_reader_xxx的接口会调用到OP-TEE的SE系统服务,用于操作具体的SE模块。

若需要在TA中操作SE模块,可将tee_se_reader_xxx类型的接口重新封装成系统调用,然后在TA中调用封装的接口就能实现TA对SE模块的操作。

在OP-TEE中要使用具体的SE模块需要初始化SE功能模块的系统服务,并挂载具体SE模块的驱动

SE模块的系统服务是通过在OP-TEE启动过程中调用tee_se_manager_init函数来实现的,该函数只会初始化该系统服务的上下文空间,函数内容如下:

        static TEE_Result tee_se_manager_init(void)
        {
            //定义SE service的上下文变量
            struct tee_se_manager_ctx *ctx = &se_manager_ctx;
            context_init(ctx);    //初始化该上下文变量的内容
            return TEE_SUCCESS;
        }

SE系统服务的上下文变量 初始化完成后,就需要挂载具体的SE模块驱动,将SE的操作接口注册到上下文中。驱动的挂载和注册过程如图17-7所示。

在这里插入图片描述

3.5 加载TA镜像的系统服务

当CA调用libteec库中用于创建与某个动态TA的会话时,会从REE侧的文件系统中加载TA镜像文件到OP-TEE,加载TA镜像的过程就会使用到该系统服务提供的接口函数。(这个系统服务是加载镜像的系统服务)

本书第13章详细介绍了OP-TEE创建会话的实现过程。OP-TEE会使用tee_ta_init_user_ta_session函数来完成加载TA镜像并初始化会话的操作。

加载TA镜像文件时,会使用user_ta_store变量中的接口发送RPC请求,通知tee_supplicant对REE侧文件系统中的TA镜像文件执行打开、读取、获取TA镜像文件大小、关闭TA镜像文件的操作。

user_ta_store变量在该系统服务启动时被赋值,具体函数内容如下:

        static const struct user_ta_store_ops ops = {
            .open = ta_open,              //发送RPC请求使tee_supplicant打开TA镜像文件
            .get_size = ta_get_size,     //发送RPC请求,获取TA镜像文件的大小
            .read = ta_read,              //发送RPC请求读取TA镜像的内容
            .close = ta_close,            //发送RPC请求关闭打开的TA镜像文件
        };
        /* OP-TEE启动时被调用,使用service_init宏将该函数编译到initcall段中 */
        static TEE_Result register_supplicant_user_ta(void)
        {
            return tee_ta_register_ta_store(&ops);
        }
        /* 将user_ta_store变量的地址赋值成ops */
        TEE_Result tee_ta_register_ta_store(const struct user_ta_store_ops *ops)
        {
            user_ta_store = ops;
            return TEE_SUCCESS;
        }

小结一下啦

以上就是关于线程的所有部分,我一直以为系统服务是一个大的,原来每个大类都有,但是如果我只有一个对应的驱动,是不是就不用了,或者说我为了以后便于拓展,也可以整一个系统服务。

以上介绍了线程的东西,也讲了线程之间的通信,还对整个TA的调用逻辑梳理了一下。这里我们应该也知道怎么去增加我们自己的安全驱动,比如我要是自己实现了加密的硬件,怎么写驱动,又怎么让用户态的TA调用到我。

其次每个TA具有独立的运行空间,OP-TEE中的一个TA调用另一个TA执行特定操作的过程是OP-TEE中的一种IPC的方式。

OP-TEE中各种系统服务起到类似框架层的作用,安全驱动或其他子模块提供的操作接口接入对应系统服务中。系统服务通过接口变量或其他方式将操作接口暴露给OP-TEE的内核空间用户空间的TA通过系统调用的方式在OP-TEE内核空间调用这些接口,从而实现TA对安全驱动或其他模块的资源操作。

这个关系最后必须再整个图看一下

在这里插入图片描述
哈哈 下一站进军中断,进军中断结束后,就来整一个实际的应用开发栗子。

内容全部来自《手机安全和可信应用开发指南》,朋友国庆快乐!!!

参考资料:
《手机安全和可信应用开发指南》


版权声明:本文为weixin_45264425原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。