Post

How to Develop a TA

How to Develop a TA

  OPTEE OS提供TEE相关基础设施,TA(Trusted Application)则是Security需求具体实现的载体。如OPTEE Overview图中所示,OPTEE OS运行在Secure EL1,而TA则运行Secure EL0,ARM在TEE端和REE端(Linux OS运行在Kernel Space,Application运行在User Space)保持了一样的设计。关于TA的开发,OPTEE官方也提供了比较详细的文档,可以对照Trusted Applications来看本篇博文,另外github上也有example来供大家参考(Hello World)。另外这里主要讲User TA。
  一个TA至少包含以下四个文件:

  • Makefile
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    # log level from 0 - 4
    # 0: none; 1: error (EMSG); 2: info (IMSG); 3: debug (DMSG); 4: flow (FMSG)
    CFG_TEE_TA_LOG_LEVEL ?= 4
    CPPFLAGS += -DCFG_TEE_TA_LOG_LEVEL=$(CFG_TEE_TA_LOG_LEVEL)
      
    # Binary name shall be the UUID of the Trusted Application
    BINARY=8aaaf200-2450-11e4-abe2-0002a5d5c51b
      
    # TA_DEV_KIT_DIR shall be set as the dir of OPTEE TA development kit
    -include $(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk
      
    ifeq ($(wildcard $(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk), )
    clean:
    	@echo 'Note: $$(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk not found, cannot clean TA'
      @echo 'Note: TA_DEV_KIT_DIR=$(TA_DEV_KIT_DIR)'
      endif
    
  • user_ta_header_defines.h
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    #ifndef USER_TA_HEADER_DEFINES_H
    #define USER_TA_HEADER_DEFINES_H
      
    /* better to expose this head file to CA too so both TA and CA can get
     * UUID from it
     */
    #include <hello_world_ta.h> /* To get the TA_HELLO_WORLD_UUID define */
      
    #define TA_UUID TA_HELLO_WORLD_UUID
      
    /*
     * TA FLAGS:
     * TA_FLAG_SINGLE_INSTANCE: declare a single instance TA.
     * TA_FLAG_MULTI_SESSION: TA can have multiple session. It only is only applied
     *                        when TA is set to single instance.
     * TA_FLAG_INSTANCE_KEEP_ALIVE: still keep alive if no session is connected. Only
     *                        available when TA_FLAG_SINGLE_INSTANCE is set.
     * TA_FLAG_SECURE_DATA_PATH: it only takes effect when CFG_SECURE_DATA_PATH is set to y.
     * TA_FLAG_CACHE_MAINTENANCE: determine if TA can do cache maintainance operation.
     */
    #define TA_FLAGS                    (TA_FLAG_MULTI_SESSION | TA_FLAG_EXEC_DDR)
    /* the size of stack of a TA instance */
    #define TA_STACK_SIZE               (2 * 1024)
    /* the size of heap of a TA instance */
    #define TA_DATA_SIZE                (32 * 1024)
      
    /* set the externed TA properties and name must not start with gpd. */
    #define TA_CURRENT_TA_EXT_PROPERTIES \
        { "gp.ta.description", USER_TA_PROP_TYPE_STRING, \
          "Hello World TA" }, \
        { "gp.ta.version", USER_TA_PROP_TYPE_U32, &(const uint32_t){ 0x0010 } }
    #endif /*USER_TA_HEADER_DEFINES_H*/
    
  • sub.mk
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    #include path of header file
    global-incdirs-y += include
    #global-incdirs-y += ../host/include
    
    #add all source files into compile. here only one
    srcs-y += hello_world_ta.c
      
    # To remove a certain compiler flag, add a line like this
    #cflags-template_ta.c-y += -Wno-strict-prototypes
    
  • [TA entry file].c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    /* TA entry points must be implemented */
    
    /* be called when an instance is created */
    TEE_Result TA_CreateEntryPoint(void) {}
    
    /* be called when an instance is destroied */
    void TA_DestroyEntryPoint(void) {}
    
    /* be called when a session is created */
    TEE_Result TA_OpenSessionEntryPoint(uint32_t param_types,
          TEE_Param __maybe_unused params[4],
          void __maybe_unused **sess_ctx)
    {return TEE_SUCCESS;}
    
    /* be called when a session is disconnected */
    void TA_CloseSessionEntryPoint(void __maybe_unused *sess_ctx) {}
    
    /* main function to handle user defined commands */
    TEE_Result TA_InvokeCommandEntryPoint(void __maybe_unused *sess_ctx,
              uint32_t cmd_id,
              uint32_t param_types, TEE_Param params[4])
    {return TEE_ERROR_BAD_PARAMETERS;}
    

      上面就是开发一个TA必须要实现的4个文件,前三个名字是固定的,第四个开发者可以自己命名,最终体现在sub.mk里。这里需要注意的点还挺多的。

UUID

  UUID是TEE OS用来identify TA的唯一字段。在OPTEE里,从load TA,create instance到opensession,都需要用到UUID。在TA的四个文件里两个地方需要设置UUID。一个是Makefile里,用来命名生成的TA binary,拿hello world例子中UUID为例,生成的ta为8aaaf200-2450-11e4-abe2-0002a5d5c51b.ta。另一个是user_ta_header_defines.h,用于赋值ta head里的字段。
  关于UUID的生成,OPTEE官方文档给了几种方法,可以翻看TA Properties这个章节。

TA_FLAGS

  TA_FLAGS的值对User TA的行为影响很大,上面加了些注释,这里还是与代码对照一下(依然以4.0.0为准)。
  第一次open session的时候,OPTEE OS会根据TA的类型来load TA并创建该TA的instance和context(这个时候context里有个变量叫ref_count会设为1),这个过程并不受这些flag的影响。第二次open session的时候,首先会根据UUID找到之前创建的context,再之后的过程就要受TA_FLAG制约了。

  • TA_FLAG_SINGLE_INSTANCE
      这个flag不设的话,该TA就是一个Multiple Instance TA,在tee_ta_init_session_with_context这个function里会返回TEE_ERROR_ITEM_NOT_FOUND。

    注意了,这个时候OPTEE OS会重新load这个TA的binary去创建一个新的instance和context。新的instance拥有独立的text, rodata, data, bss, stack和heap。也就是open session几次,它就占用这些空间几份。这对tee内存的消耗是巨大的,所以使用multiple instance TA的时候要特别注意memory的使用情况。

    https://github.com/OP-TEE/optee_os/blob/4.0.0/core/kernel/tee_ta_manager.c#L556

    1
    2
    3
    4
    5
    6
    7
    8
    
      /*
       * If TA isn't single instance it should be loaded as new
       * instance instead of doing anything with this instance.
       * So tell the caller that we didn't find the TA it the
       * caller will load a new instance.
       */
      if ((ctx->flags & TA_FLAG_SINGLE_INSTANCE) == 0)
          return TEE_ERROR_ITEM_NOT_FOUND;
    

      那么设了这个flag,这个TA就是single instance的TA了,这就涉及到下面两个flag了。

  • TA_FLAG_MULTI_SESSION
      继续看tee_ta_init_session_with_context这个function。
    https://github.com/OP-TEE/optee_os/blob/4.0.0/core/kernel/tee_ta_manager.c#L563

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    /*
       * The TA is single instance, if it isn't multi session we
       * can't create another session unless its reference is zero
       */
      if (!(ctx->flags & TA_FLAG_MULTI_SESSION) && ctx->ref_count)
          return TEE_ERROR_BUSY;
      DMSG("Re-open TA %pUl", (void *)&ctx->ts_ctx.uuid);
    
      ctx->ref_count++;
      s->ts_sess.ctx = &ctx->ts_ctx;
      s->ts_sess.handle_scall = s->ts_sess.ctx->ops->handle_scall;
      return TEE_SUCCESS;
    

      OPTEE首先check TA的flag是否是TA_FLAG_MULTI_SESSION。是的话ref_count++,并把找到的该TA的ctx赋值给本session。如果是single session,ref_count为0的话(这里注意ref_count什么情况下为0),也继续走下去;ref_count不为0,表示已经有session存在,就不能open一个新的session了。
      这里讲个事,linaro官方给的例子竟然出现了差错。具体位置在这里https://github.com/linaro-swg/hello_world/blob/master/ta/user_ta_header_defines.h#L39。例子里没有设TA_FLAG_SINGLE_INSTANCE这个flag说明TA是multiple instance的,那么单独设TA_FLAG_MULTI_SESSION是没有意义的。

    1
    
    #define TA_FLAGS                    (TA_FLAG_MULTI_SESSION | TA_FLAG_EXEC_DDR)
    
  • TA_FLAG_INSTANCE_KEEP_ALIVE
      TA_FLAG_MULTI_SESSION是在open session的时候影响OPTEE的行为。而TA_FLAG_INSTANCE_KEEP_ALIVE则是在close session的时候起作用。直接上代码:
    https://github.com/OP-TEE/optee_os/blob/4.0.0/core/kernel/tee_ta_manager.c#L512

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    ctx->ref_count--;
      keep_alive = (ctx->flags & TA_FLAG_INSTANCE_KEEP_ALIVE) &&
              (ctx->flags & TA_FLAG_SINGLE_INSTANCE);
      if (!ctx->ref_count && (ctx->panicked || !keep_alive)) {
          if (!ctx->is_releasing) {
              TAILQ_REMOVE(&tee_ctxes, ctx, link);
              ctx->is_releasing = true;
          }
          mutex_unlock(&tee_ta_mutex);
    
          destroy_context(ctx);
      } else
          mutex_unlock(&tee_ta_mutex);
    

      close session首先是把ref_count–。keep_alive这个值取决于两个flag,TA_FLAG_INSTANCE_KEEP_ALIVE和TA_FLAG_SINGLE_INSTANCE,两个有一个没有设则keep_alive既为false。
      先说multiple instance的情况(TA_FLAG_SINGLE_INSTANCE没有设),keep_alive一定为false,创建instance的时候ref_count设为1,multiple instance的TA每个instance只能对应一个session(如上解释,再次open session会新建一个instance)。ref_count–后其值为0.这样以下条件为true,destroy_context(ctx)被调用,该instance被destroy。

    1
    
    if (!ctx->ref_count && (ctx->panicked || !keep_alive)) {
    

      TA_FLAG_SINGLE_INSTANCE设了的情况下,keep_alive的值取决于TA_FLAG_INSTANCE_KEEP_ALIVE。如果设了,即便ref_count为0,keep_alive为true也不会进入if里面(不考虑panic的情况),这是虽然ref_count减为0,TA的instance和context还能保住。
      相反,没有设这个flag,keep_alive为false,就要看ref_count也就是还在open的session个数了,最后一个session close的时候,就是instance和context挂掉的时候。

    下次再open session会是一个全新的instance和context,context里所有的内容恢复为默认值。

  TA_FLAG_SECURE_DATA_PATH和TA_FLAG_CACHE_MAINTENANCE比较简单,前一个决定TA是否参与Secure Data Path,既能否访问定义为Secure Data的memory;后一个决定TA是否可以进行cache的flush,clean和invalidate,主要用于协调不在同一个cache coherent domain的master之间的数据访问。

TA Entry Points

  总共五个必须实现的entry point,前四个不必多讲,TA_InvokeCommandEntryPoint是日常CA请求TA的secure service的入口函数。Linaro的example给了很好的模板,开发者定义自己的command ID和相应处理函数,按模板填入即可。
https://github.com/linaro-swg/hello_world/blob/master/ta/hello_world_ta.c#L118

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
TEE_Result TA_InvokeCommandEntryPoint(void __maybe_unused *sess_ctx,
			uint32_t cmd_id,
			uint32_t param_types, TEE_Param params[4])
{
	(void)&sess_ctx; /* Unused parameter */

	switch (cmd_id) {
	case TA_HELLO_WORLD_CMD_INC_VALUE:
		return inc_value(param_types, params);
#if 0
	case TA_HELLO_WORLD_CMD_XXX:
		return ...
		break;
	case TA_HELLO_WORLD_CMD_YYY:
		return ...
		break;
	case TA_HELLO_WORLD_CMD_ZZZ:
		return ...
		break;
	...
#endif
	default:
		return TEE_ERROR_BAD_PARAMETERS;
	}
}

TEE Internal APIs

  前面有提过TA通过syscall来请求OPTEE OS提供的服务,而这些standard服务基本上上包装在GPD定义的TEE Internal API里。有以下几类:

  • Trusted Core Framework API
    TEE_Malloc , TEE_Free, TEE_Panic, TEE_OpenTASession, TEE_InvokeTACommand, TEE_CloseTASession
  • Trusted Storage API for Data and Keys
    TEE_CreatePersistentObject, TEE_OpenPersistentObject, TEE_ReadObjectData, TEE_WriteObjectData
  • Cryptographic Operations API
    message digest, symmetric cipher, MAC, Authenticated encryption, asymmetric cipher, key derivation and RNG
  • Time API
    TEE_Wait, TEE_GetSystemTime, TEE_GetREETime
  • TEE Arithmetical API
    TEE_BigIntConvertFromOctetString

  这里列了一些主要函数,具体用法参看TEE Internal Core API Specification v1.3.1

  好了,又总结了一篇,开心。

References:

Trusted Applications
Hello World
TEE Internal Core API Specification v1.3.1

This post is licensed under CC BY 4.0 by the author.