动态UTA按照存储位置的不同分为REE filesystem TA:存放在REE侧文件系统里的TA;
Early TA:被嵌入到optee os里的在supplicant启动之前就可用了。
这里我们讲的是常规的存放在REE侧文件系统里的TA。
通过GP标准调用的与TA通信的命令(opensession\invoke\closession)其实都是std smc call,该smc调用后,会进入到TEE中的tee_entry_std中。
在__tee_entry_std函数中调用就是opensession\invoke\closession的接口,调用到entry_open_session
接口
动态UTA
最终会在tee_ta_init_user_ta_session
函数里加载。ldelf_load_ldelf
函数,其实现大致为:ldelf.elf
二进制文件,通过gen_ldelf_hex.py
脚本将ldelf.elf
二进制文件生成ldelf_hex.c
文件,其二进制数据会被写入数组ldelf_data
,还有代码段,数据段的大小ldelf_load_ldelf
函数中将ldelf_data
加载到内存中ldelf_init_with_ldelf
函数调用thread_enter_user_mode
函数切换到用户模式el0,运行ldelf的代码PTA
最终tee_ta_init_pseudo_ta_session
函数里加载elf格式的文件是一种可执行文件,ldelf就是加载elf文件的加载器。
而TA文件就是加了头的elf文件,所以ldelf其作用为load需要加载的TA。
调用ldelf → ta_elf_load_main → load_main加载TA的二进制文件。
optee_os\ldelf\start_a64.S
https://blog.csdn.net/qq_42878531/article/details/121783732
验签、解密、加载TA二进制文件,并判断其是否是ELF格式。
系统调用 ldelf_syscall_open_bin 函数,其功能是调用systemPTA,找到并打开TA文件,并返回句柄。
此函数会遍历注册的TA_STORE,调用open等操作函数。注册的是下面这个。
TEE_TA_REGISTER_TA_STORE(9) = {
.description = "REE",
.open = ree_fs_ta_open,
.get_size = ree_fs_ta_get_size,
.get_tag = ree_fs_ta_get_tag,
.read = ree_fs_ta_read,
.close = ree_fs_ta_close,
};
/* Request TA from tee-supplicant */
res = rpc_load(uuid, &ta, &ta_size, &mobj);
通过rpc_load()之后得到的TA加载的共享内存地址,因为TA镜像在签名时增加签名相关的头部文件shdr,在此函数中计算shdr的大小,struct shdr + hash_size + sig_size。然后申请此大小的内存并将TA镜像的shdr拷贝到申请的内存里。用于后面的验签流程。
/* Make secure copy of signed header */
shdr = shdr_alloc_and_copy(ta, ta_size);
执行验签工作。传入镜像头部文件shdr指针。shdr包括了magic、img_type、img_size、algo等参数。先检验代码中的magic与shdr中的是否一致,从前面提到的ta_pub_key.c中读取ta_pub_key_modulus,提取出pub_key,从shdr中提取hash和sig,使用rsa算法验签此shdr。
/* Validate header signature */
res = shdr_verify_signature(shdr);
然后在ree_fs_ta_open()里还要取出TA镜像里的uuid与传入的uuid比较,并初始化hash_ctx。
ldelf_syscall_map_bin,其功能是调用systemPTA,映射TA二进制文件的各种segment到安全内存中。
res = sys_map_ta_bin(&va, SMALL_PAGE_SIZE, flags, elf->handle, 0, 0, 0);
在init_elf中,此处是第一次调用,传入的长度是0x1000,最小页大小。目的为先将TA的从0到0x1000的数据映射到安全内存。主要作用先申请块安全内存,然后映射其地址。
struct fobj *f = fobj_ta_mem_alloc(num_pages);
mobj = mobj_with_fobj_alloc(f, file);
res = vm_map_pad(uctx, va, num_rounded_bytes,
TEE_MATTR_PRW, vm_flags, mobj, 0,
pad_begin, pad_end, 0);
经过这几个步骤,其就会申请了大小为num_pages个pages的空间,并映射到va_range的地址空间里,调试出的信息就是申请的是phys
安全内存物理地址,变为了va
的虚拟地址(fobj_ta_mem_alloc)。
然后调用binh_copy_to函数,此函数调用注册的ree_fs_ta_read,将共享内存中的TA镜像加载到安全内存中。
/*此函数是根据binh句柄,其中记录了当前读取ELF文件的偏移,文件总大小的信息,
来从offs_bytes在文件中的偏移,读取num_bytes长度的数据到va这个地址,其
调用注册的read接口,也就是ree_fs_ta_read
*/
static TEE_Result binh_copy_to(struct bin_handle *binh, vaddr_t va,
size_t offs_bytes, size_t num_bytes)
{
TEE_Result res = TEE_SUCCESS;
size_t next_offs = 0;
if (offs_bytes < binh->offs_bytes)
return TEE_ERROR_BAD_STATE;
if (ADD_OVERFLOW(offs_bytes, num_bytes, &next_offs))
return TEE_ERROR_BAD_PARAMETERS;
if (offs_bytes > binh->offs_bytes) {
/*此处需要读取文件的偏移已经大于了文件所处的偏移,因为ELF文件中的segment
之间存在空的部分,如果不读取中见的信息,那么ree_fs_ta_read里更新
hash_ctx就会出问题,导致校验hash不过,所以传入空的地址给read函数,
只是为了读取空的部分更新hash_ctx,并不加载*/
res = binh->op->read(binh->h, NULL,
offs_bytes - binh->offs_bytes);
if (res)
return res;
binh->offs_bytes = offs_bytes;
}
if (next_offs > binh->size_bytes) {
/*这种情况就是需要读取的部分已经超过了文件的范围,所以此时直接将文件
剩下的全部读取*/
size_t rb = binh->size_bytes - binh->offs_bytes;
res = binh->op->read(binh->h, (void *)va, rb);
if (res)
return res;
memset((uint8_t *)va + rb, 0, num_bytes - rb);
binh->offs_bytes = binh->size_bytes;
} else {
res = binh->op->read(binh->h, (void *)va, num_bytes);
if (res)
return res;
binh->offs_bytes = next_offs;
}
return TEE_SUCCESS;
}
static TEE_Result ree_fs_ta_read(struct user_ta_store_handle *h, void *data,
size_t len)
{
struct ree_fs_ta_handle *handle = (struct ree_fs_ta_handle *)h;
/*获取TA镜像除去头之后的elf格式所在位置*/
uint8_t *src = (uint8_t *)handle->nw_ta + handle->offs;
size_t next_offs = 0;
uint8_t *dst = src;
TEE_Result res = TEE_SUCCESS;
if (ADD_OVERFLOW(handle->offs, len, &next_offs) ||
next_offs > handle->nw_ta_size)
return TEE_ERROR_BAD_PARAMETERS;
/*判断TA类型*/
if (handle->shdr->img_type == SHDR_ENCRYPTED_TA) {
if (data) {
dst = data; /* Hash secure buffer */
/*解密len长度的数据到安全内存中,加载多长的数据就解密多长的数据*/
res = tee_ta_decrypt_update(handle->enc_ctx, dst, src,
len);
if (res != TEE_SUCCESS)
return TEE_ERROR_SECURITY;
} else {
size_t num_bytes = 0;
size_t b_size = MIN(1024U, len);
uint8_t *b = malloc(b_size);
if (!b)
return TEE_ERROR_OUT_OF_MEMORY;
dst = NULL;
while (num_bytes < len) {
size_t n = MIN(b_size, len - num_bytes);
res = tee_ta_decrypt_update(handle->enc_ctx, b,
src + num_bytes, n);
if (res)
break;
num_bytes += n;
res = crypto_hash_update(handle->hash_ctx, b,
n);
if (res)
break;
}
free(b);
if (res != TEE_SUCCESS)
return TEE_ERROR_SECURITY;
}
} else if (data) { /*不是加密的TA则直接拷贝*/
dst = data; /* Hash secure buffer (shm might be modified) */
memcpy(dst, src, len);
}
if (dst) {
/*更新hash_ctx,以便后面计算出TA的hash值使用*/
res = crypto_hash_update(handle->hash_ctx, dst, len);
if (res != TEE_SUCCESS)
return TEE_ERROR_SECURITY;
}
handle->offs = next_offs;/*将句柄的偏移更新*/
/*判断当前是否已经加载了所有的segment*/
if (handle->offs == handle->nw_ta_size) {
if (handle->shdr->img_type == SHDR_ENCRYPTED_TA) {
/*
* Last read: time to finalize authenticated
* decryption.
*/
res = tee_ta_decrypt_final(handle->enc_ctx,
handle->ehdr, NULL, NULL, 0);
if (res != TEE_SUCCESS)
return TEE_ERROR_SECURITY;
}
/*
* Last read: time to check if our digest matches the expected
* one (from the signed header)
*/
/*加载所有segment之后进行hash校验与shdr里的比较*/
res = check_digest(handle);
if (res != TEE_SUCCESS)
return res;
if (handle->bs_hdr)
res = check_update_version(handle->bs_hdr);
}
return res;
}
由此时传入的参数得知,此次的sys_map_ta_bin只是将TA的elf文件的头映射到了内存中。
有了头就可以根据elf头判断其基本信息了。
完成这些之后,程序会解析判断其elf格式
if (!IS_ELF(*(Elf32_Ehdr *)va))/*根据elf head magic判断*/
err(TEE_ERROR_BAD_FORMAT, "TA is not an ELF");
res = e32_parse_ehdr(elf, (void *)va);
if (res == TEE_ERROR_BAD_FORMAT)
res = e64_parse_ehdr(elf, (void *)va);/*解析elf head到elf结构体*/
if (res)
err(res, "Cannot parse ELF");
if (MUL_OVERFLOW(elf->e_phnum, elf->e_phentsize, &sz) ||
ADD_OVERFLOW(sz, elf->e_phoff, &sz))
err(TEE_ERROR_BAD_FORMAT, "Program headers size overflow");
if (sz > SMALL_PAGE_SIZE)
err(TEE_ERROR_NOT_SUPPORTED, "Cannot read program headers");
elf->phdr = (void *)(va + elf->e_phoff);
ELF格式的文件加载到安全内存之后,加载器会判断其是否是ELF格式的文件,以及头是否符合elf规范。
ELF格式分两种,32位和64位,基本一样。格式的基本信息包含在ELF的头里,这个是通用的,使用此结构体表示。
/*
* ELF header.
*/
typedef struct {
unsigned char e_ident[EI_NIDENT]; /* File identification. */
Elf64_Half e_type; /* File type. */
Elf64_Half e_machine; /* Machine architecture. */
Elf64_Word e_version; /* ELF format version. */
Elf64_Addr e_entry; /* Entry point. */
Elf64_Off e_phoff; /* Program header file offset. */
Elf64_Off e_shoff; /* Section header file offset. */
Elf64_Word e_flags; /* Architecture-specific flags. */
Elf64_Half e_ehsize; /* Size of ELF header in bytes. */
Elf64_Half e_phentsize; /* Size of program header entry. */
Elf64_Half e_phnum; /* Number of program header entries. */
Elf64_Half e_shentsize; /* Size of section header entry. */
Elf64_Half e_shnum; /* Number of section header entries. */
Elf64_Half e_shstrndx; /* Section name strings section. */
} Elf64_Ehdr;
elf head头后面就是程序头表program segment header,位置就是ELF头里的e_phnum值在的偏移。记录了每个Segment的相关信息,比如类型、对应文件的偏移、大小、属性等。用下面的结构体表示。
typedef struct {
Elf64_Word p_type; /* Entry type. */
Elf64_Word p_flags; /* Access permission flags. */
Elf64_Off p_offset; /* File offset of contents. */
Elf64_Addr p_vaddr; /* Virtual address in memory image. */
Elf64_Addr p_paddr; /* Physical address (not used). */
Elf64_Xword p_filesz; /* Size of contents in file. */
Elf64_Xword p_memsz; /* Size of contents in memory. */
Elf64_Xword p_align; /* Alignment in memory and file. */
} Elf64_Phdr;
optee example的一个TA的elf读出其program header:
aarch64-linux-gnu-readelf -l xxx.elf
总结:找到TA文件,验签成功则解密0x1000头部数据,加载0x1000字节到内存,判断其是否是elf以及是否合法。