Session Login Methods and Possible Improvement
CA调用TA的secure service之前,首先要创建一个连接,这个连接叫session。由于这个连接是从REE发起的,对session的安全性有一定的担忧,比如有需求要求某个TA只服务于特定CA。OPTEE其实提供了方案,但整个代码看下来,实在是比较鸡肋。接下来先分析代码,然后看怎么改进比较合适。
Note:本文中引用的代码版本如下:
OPTEE: 4.0.0
Linux: v5.15
Current Situation
OPTEE提供的方案叫做connection method。有以下几种:
https://github.com/OP-TEE/optee_client/blob/4.0.0/libteec/include/tee_client_api.h#L224
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Session login methods, for use in TEEC_OpenSession() as parameter
* connectionMethod. Type is uint32_t.
*
* TEEC_LOGIN_PUBLIC No login data is provided.
* TEEC_LOGIN_USER Login data about the user running the Client
* Application process is provided.
* TEEC_LOGIN_GROUP Login data about the group running the Client
* Application process is provided.
* TEEC_LOGIN_APPLICATION Login data about the running Client Application
* itself is provided.
* TEEC_LOGIN_USER_APPLICATION Login data about the user and the running
* Client Application itself is provided.
* TEEC_LOGIN_GROUP_APPLICATION Login data about the group and the running
* Client Application itself is provided.
*/
#define TEEC_LOGIN_PUBLIC 0x00000000
#define TEEC_LOGIN_USER 0x00000001
#define TEEC_LOGIN_GROUP 0x00000002
#define TEEC_LOGIN_APPLICATION 0x00000004
#define TEEC_LOGIN_USER_APPLICATION 0x00000005
#define TEEC_LOGIN_GROUP_APPLICATION 0x00000006
Connection method是在open session的时候指定的。函数如下:
https://github.com/OP-TEE/optee_client/blob/4.0.0/libteec/include/tee_client_api.h#L468
1
2
3
4
5
6
7
8
TEEC_Result TEEC_OpenSession(TEEC_Context *context,
TEEC_Session *session,
const TEEC_UUID *destination,
uint32_t connectionMethod,
const void *connectionData,
TEEC_Operation *operation,
uint32_t *returnOrigin);
来看这个connectionMethod传下去都做了什么处理。
https://github.com/OP-TEE/optee_client/blob/4.0.0/libteec/src/tee_client_api.c#L543
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
static void setup_client_data(struct tee_ioctl_open_session_arg *arg,
uint32_t connection_method,
const void *connection_data)
{
arg->clnt_login = connection_method;
switch (connection_method) {
case TEE_IOCTL_LOGIN_PUBLIC:
/* No connection data to pass */
break;
case TEE_IOCTL_LOGIN_USER:
/* Kernel auto-fills UID and forms client UUID */
break;
case TEE_IOCTL_LOGIN_GROUP:
/*
* Connection data for group login is uint32_t and rest of
* clnt_uuid is set as zero.
*
* Kernel verifies group membership and then forms client UUID.
*/
memcpy(arg->clnt_uuid, connection_data, sizeof(gid_t));
break;
case TEE_IOCTL_LOGIN_APPLICATION:
/*
* Kernel auto-fills application identifier and forms client
* UUID.
*/
break;
case TEE_IOCTL_LOGIN_USER_APPLICATION:
/*
* Kernel auto-fills application identifier, UID and forms
* client UUID.
*/
break;
case TEE_IOCTL_LOGIN_GROUP_APPLICATION:
/*
* Connection data for group login is uint32_t rest of
* clnt_uuid is set as zero.
*
* Kernel verifies group membership, auto-fills application
* identifier and then forms client UUID.
*/
memcpy(arg->clnt_uuid, connection_data, sizeof(gid_t));
break;
default:
/*
* Unknown login method, don't pass any connection data as we
* don't know size.
*/
break;
}
}
这里TEE_IOCTL_LOGIN_XXXX和TEE_LOGIN_XXXX一一对应。
connection method保存在arg->clnt_login里往后传。通过注释,TEE_IOCTL_LOGIN_PUBLIC是放弃security了:grin:,其他的都说kernel会form一个client UUID。其中TEE_IOCTL_LOGIN_GROUP和TEE_IOCTL_LOGIN_GROUP_APPLICATION还copy了connection_data到arg->clnt_uuid。这里比较费解,在TEEC_OpenSession对参数的注释里这样描述:
https://github.com/OP-TEE/optee_client/blob/4.0.0/libteec/include/tee_client_api.h#L453
1
2
3
* @param connectionData Any data necessary to connect with the chosen
* connection method. Not supported, should be set to
* NULL.
注释里说设成NULL,那memcpy岂不是会出错?去翻TEE_Client_API_Specification,这里有了正确的解释:
1
2
3
connectionData MUST point to a uint32_t which contains the group which this Client
Application wants to connect as. The Implementation is responsible for securely ensuring
that the Client Application instance is actually a member of this group.
所以当connection method为TEEC_LOGIN_GROUP或TEEC_LOGIN_GROUP_APPLICATION时,connectionData不能为NULL,要指向一个32位的值,这个值包含了组信息。
继续看,TEEC_OpenSession通过ioctl进入到了Linux Kernel里。Kernel在driver/tee/tee_core.c
里处理了这个ioctl。Linux给了两套处理函数,SMC ABI和FFA ABI,本文基于SMC ABI继续跟踪(取决于Linux DTS里OPTEE的method设定为smc)。这两者的区别以后有时间再写博文补课。ioctl在kernel里通过filp->private_data拿到tee_context, 获取到tee_device的desc中的ops,调用open_session函数,而这个函数指向optee_open_session(https://github.com/torvalds/linux/blob/v5.15/drivers/tee/optee/call.c#L213)。然后取出arg->clnt_login和arg->clnt_uuid,通过tee_session_calc_client_uuid生成client_uuid。
https://github.com/torvalds/linux/blob/v5.15/drivers/tee/tee_core.c#L194
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
int tee_session_calc_client_uuid(uuid_t *uuid, u32 connection_method,
const u8 connection_data[TEE_IOCTL_UUID_LEN])
{
gid_t ns_grp = (gid_t)-1;
kgid_t grp = INVALID_GID;
char *name = NULL;
int name_len;
int rc;
if (connection_method == TEE_IOCTL_LOGIN_PUBLIC ||
connection_method == TEE_IOCTL_LOGIN_REE_KERNEL) {
/* Nil UUID to be passed to TEE environment */
uuid_copy(uuid, &uuid_null);
return 0;
}
/*
* In Linux environment client UUID is based on UUIDv5.
*
* Determine client UUID with following semantics for 'name':
*
* For TEEC_LOGIN_USER:
* uid=<uid>
*
* For TEEC_LOGIN_GROUP:
* gid=<gid>
*
*/
name = kzalloc(TEE_UUID_NS_NAME_SIZE, GFP_KERNEL);
if (!name)
return -ENOMEM;
switch (connection_method) {
case TEE_IOCTL_LOGIN_USER:
name_len = snprintf(name, TEE_UUID_NS_NAME_SIZE, "uid=%x",
current_euid().val);
if (name_len >= TEE_UUID_NS_NAME_SIZE) {
rc = -E2BIG;
goto out_free_name;
}
break;
case TEE_IOCTL_LOGIN_GROUP:
memcpy(&ns_grp, connection_data, sizeof(gid_t));
grp = make_kgid(current_user_ns(), ns_grp);
if (!gid_valid(grp) || !in_egroup_p(grp)) {
rc = -EPERM;
goto out_free_name;
}
name_len = snprintf(name, TEE_UUID_NS_NAME_SIZE, "gid=%x",
grp.val);
if (name_len >= TEE_UUID_NS_NAME_SIZE) {
rc = -E2BIG;
goto out_free_name;
}
break;
default:
rc = -EINVAL;
goto out_free_name;
}
rc = uuid_v5(uuid, &tee_client_uuid_ns, name, name_len);
out_free_name:
kfree(name);
return rc;
}
EXPORT_SYMBOL_GPL(tee_session_calc_client_uuid);
到这里忽然发现TEEC_LOGIN_APPLICATION,TEEC_LOGIN_USER_APPLICATION和TEEC_LOGIN_GROUP_APPLICATION都成default不处理了。这几个暂时还是不要用了。在TEE_IOCTL_LOGIN_USER的处理里拿到当前process的euid,而TEE_IOCTL_LOGIN_GROUP则是得到gid,把他们转换成uid=xxxx或gid=xxxx的string形式,送给uuid_v51生成client uuid。打包在optee_msg_arg里,然后送给optee。
client uuid是在Linux kernel里生成的 中间经过secure monitor,world switch,optee std_smc_entry->std_entry_with_parg->call_entry_std->tee_entry_std->__tee_entry_std->entry_open_session这里就不细分析了。直接看client_uuid的处理。
https://github.com/OP-TEE/optee_os/blob/4.0.0/core/tee/entry_std.c#L315
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tee_uuid_from_octets(uuid, (void *)¶ms[0].u.value);
clnt_id->login = params[1].u.value.c;
switch (clnt_id->login) {
case TEE_LOGIN_PUBLIC:
case TEE_LOGIN_REE_KERNEL:
memset(&clnt_id->uuid, 0, sizeof(clnt_id->uuid));
break;
case TEE_LOGIN_USER:
case TEE_LOGIN_GROUP:
case TEE_LOGIN_APPLICATION:
case TEE_LOGIN_APPLICATION_USER:
case TEE_LOGIN_APPLICATION_GROUP:
tee_uuid_from_octets(&clnt_id->uuid,
(void *)¶ms[1].u.value);
break;
default:
return TEE_ERROR_BAD_PARAMETERS;
}
很直接,copy在TEE_Identity clnt_id里。然后在tee_ta_open_session里把它保存在TA session的数据结构里。
https://github.com/OP-TEE/optee_os/blob/4.0.0/core/kernel/tee_ta_manager.c#L677
1
2
3
4
5
6
7
....
struct tee_ta_session *s = NULL;
....
res = tee_ta_init_session(err, open_sessions, uuid, &s);
....
/* Save identity of the owner of the session */
s->clnt_id = *clnt_id;
对于TEE_Identity clnt_id的应用,我们从OPTEE回溯到Linux和CA来看,为什么反着看,一会自然明白。
OPTEE定义了一个function叫做check_client来检查client id(uuid)是否匹配。link放在下面,代码就不copy了。
https://github.com/OP-TEE/optee_os/blob/4.0.0/core/kernel/tee_ta_manager.c#L349
这个函数分别在tee_ta_invoke_command,tee_ta_cancel_command和tee_ta_close_session里调用。嗯,看到这里还不错,该有的检查都有。继续回溯,看作比较的两个值都从哪里得来。不看TA,只看从REE调用过来的function。三个函数的路径分别是(反着来):
1
2
3
4
5
tee_ta_invoke_command<-entry_invoke_command<-__tee_entry_std<-tee_entry_std<-call_entry_std<-std_entry_with_parg<-std_smc_entry<-__thread_std_smc_entry
tee_ta_cancel_command<-entry_cancel<-__tee_entry_std (the following is the same)
tee_ta_close_session<-entry_close_session<-__tee_entry_std (the following is the same)
然而忽然发现TEE_Identity *clnt_id这个参数统一设为NSAPP_IDENTITY,即为NULL,并且在check_client函数里,NULL的情况竟然返回TEE_SUCCESS。真是大失所望!!!
1
2
3
4
5
6
7
if (id == NSAPP_IDENTITY) {
if (s->clnt_id.login == TEE_LOGIN_TRUSTED_APP) {
DMSG("nsec tries to hijack TA session");
return TEE_ERROR_ACCESS_DENIED;
}
return TEE_SUCCESS;
}
再去Linux Kernel里,发现uuid v5只有open session的时候调用了。然后就没了……。这……。
Possible Method
在进行改造之前,首先明确需求,就是在第一次open session完成后,只允许该process或者该process所属group中所有的process和TA进行通讯。鉴于基本的结构已经有了,改进方法也比较简单。
- OPTEE OS中把TEE_Identity存在instance的ctx里,而非session里,这样multiple session的TA从ctx的TEE_Identity得到验证。
- 如果是multiple session,此后再次open session应该用同样的connection method。OPTEE OS端如果已经有ctx存在,验证本次传过来的client uuid是否正确。不正确返回失败。
- connection method应保存在session数据结构中。
- invoke, cancel, close操作在Linux Kernel中同样需要根据session中method生成client uuid。OPTEE OS对该操作验证TEE_Identity。不成功返回失败。
Reference
Optee Client
Optee OS
Linux v5.4
TEE_Client_API_Specification
Note
UUID type5。Do SHA1 hash first and output a 20 bytes digest. Keep the first 16 bytes. Change the high-nibble of byte 6 to 5 and set upper two bits of byte 8 to 0b10. ↩︎