* Copyright (c) 2018-2019, 2022 Linaro Limited
* Copyright (c) 2020-2021, Arm Limited
*/
#include <assert.h>
#include <crypto/crypto.h>
#include <kernel/ldelf_syscalls.h>
#include <kernel/user_mode_ctx.h>
#include <ldelf.h>
#include <mm/file.h>
#include <mm/fobj.h>
#include <mm/mobj.h>
#include <mm/vm.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
#include <util.h>
struct bin_handle {
const struct ts_store_ops *op;
struct ts_store_handle *h;
struct file *f;
size_t offs_bytes;
size_t size_bytes;
};
TEE_Result ldelf_syscall_map_zi(vaddr_t *va, size_t num_bytes, size_t pad_begin,
size_t pad_end, unsigned long flags)
{
TEE_Result res = TEE_SUCCESS;
struct ts_session *sess = ts_get_current_session();
struct user_mode_ctx *uctx = to_user_mode_ctx(sess->ctx);
struct fobj *f = NULL;
struct mobj *mobj = NULL;
uint32_t prot = TEE_MATTR_URW | TEE_MATTR_PRW;
uint32_t vm_flags = 0;
if (flags & ~LDELF_MAP_FLAG_SHAREABLE)
return TEE_ERROR_BAD_PARAMETERS;
if (flags & LDELF_MAP_FLAG_SHAREABLE)
vm_flags |= VM_FLAG_SHAREABLE;
f = fobj_ta_mem_alloc(ROUNDUP_DIV(num_bytes, SMALL_PAGE_SIZE));
if (!f)
return TEE_ERROR_OUT_OF_MEMORY;
mobj = mobj_with_fobj_alloc(f, NULL, TEE_MATTR_MEM_TYPE_TAGGED);
fobj_put(f);
if (!mobj)
return TEE_ERROR_OUT_OF_MEMORY;
res = vm_map_pad(uctx, va, num_bytes, prot, vm_flags,
mobj, 0, pad_begin, pad_end, 0);
mobj_put(mobj);
return res;
}
TEE_Result ldelf_syscall_unmap(vaddr_t va, size_t num_bytes)
{
TEE_Result res = TEE_SUCCESS;
struct ts_session *sess = ts_get_current_session();
struct user_mode_ctx *uctx = to_user_mode_ctx(sess->ctx);
size_t sz = ROUNDUP(num_bytes, SMALL_PAGE_SIZE);
uint32_t vm_flags = 0;
vaddr_t end_va = 0;
* The vm_get_flags() and vm_unmap() are supposed to detect or handle
* overflow directly or indirectly. However, since this function is an
* API function it's worth having an extra guard here. If nothing else,
* to increase code clarity.
*/
if (ADD_OVERFLOW(va, sz, &end_va))
return TEE_ERROR_BAD_PARAMETERS;
res = vm_get_flags(uctx, va, sz, &vm_flags);
if (res)
return res;
if (vm_flags & VM_FLAG_PERMANENT)
return TEE_ERROR_ACCESS_DENIED;
return vm_unmap(uctx, va, sz);
}
static void bin_close(void *ptr)
{
struct bin_handle *binh = ptr;
if (binh) {
if (binh->op && binh->h)
binh->op->close(binh->h);
file_put(binh->f);
}
free(binh);
}
TEE_Result ldelf_syscall_open_bin(const TEE_UUID *uuid, size_t uuid_size,
uint32_t *handle)
{
TEE_Result res = TEE_SUCCESS;
struct ts_session *sess = ts_get_current_session();
struct user_mode_ctx *uctx = to_user_mode_ctx(sess->ctx);
struct system_ctx *sys_ctx = sess->user_ctx;
struct bin_handle *binh = NULL;
uint8_t tag[FILE_TAG_SIZE] = { 0 };
unsigned int tag_len = sizeof(tag);
int h = 0;
res = vm_check_access_rights(uctx,
TEE_MEMORY_ACCESS_READ |
TEE_MEMORY_ACCESS_ANY_OWNER,
(uaddr_t)uuid, sizeof(TEE_UUID));
if (res)
return res;
res = vm_check_access_rights(uctx,
TEE_MEMORY_ACCESS_WRITE |
TEE_MEMORY_ACCESS_ANY_OWNER,
(uaddr_t)handle, sizeof(uint32_t));
if (res)
return res;
if (uuid_size != sizeof(*uuid))
return TEE_ERROR_BAD_PARAMETERS;
if (!sys_ctx) {
sys_ctx = calloc(1, sizeof(*sys_ctx));
if (!sys_ctx)
return TEE_ERROR_OUT_OF_MEMORY;
sess->user_ctx = sys_ctx;
}
binh = calloc(1, sizeof(*binh));
if (!binh)
return TEE_ERROR_OUT_OF_MEMORY;
if (is_user_ta_ctx(sess->ctx) || is_stmm_ctx(sess->ctx)) {
SCATTERED_ARRAY_FOREACH(binh->op, ta_stores,
struct ts_store_ops) {
DMSG("Lookup user TA ELF %pUl (%s)",
(void *)uuid, binh->op->description);
res = binh->op->open(uuid, &binh->h);
DMSG("res=%#"PRIx32, res);
if (res != TEE_ERROR_ITEM_NOT_FOUND &&
res != TEE_ERROR_STORAGE_NOT_AVAILABLE)
break;
}
} else if (is_sp_ctx(sess->ctx)) {
SCATTERED_ARRAY_FOREACH(binh->op, sp_stores,
struct ts_store_ops) {
DMSG("Lookup user SP ELF %pUl (%s)",
(void *)uuid, binh->op->description);
res = binh->op->open(uuid, &binh->h);
DMSG("res=%#"PRIx32, res);
if (res != TEE_ERROR_ITEM_NOT_FOUND &&
res != TEE_ERROR_STORAGE_NOT_AVAILABLE)
break;
}
} else {
res = TEE_ERROR_ITEM_NOT_FOUND;
}
if (res)
goto err;
res = binh->op->get_size(binh->h, &binh->size_bytes);
if (res)
goto err;
res = binh->op->get_tag(binh->h, tag, &tag_len);
if (res)
goto err;
binh->f = file_get_by_tag(tag, tag_len);
if (!binh->f)
goto err_oom;
h = handle_get(&sys_ctx->db, binh);
if (h < 0)
goto err_oom;
*handle = h;
return TEE_SUCCESS;
err_oom:
res = TEE_ERROR_OUT_OF_MEMORY;
err:
bin_close(binh);
return res;
}
TEE_Result ldelf_syscall_close_bin(unsigned long handle)
{
TEE_Result res = TEE_SUCCESS;
struct ts_session *sess = ts_get_current_session();
struct system_ctx *sys_ctx = sess->user_ctx;
struct bin_handle *binh = NULL;
if (!sys_ctx)
return TEE_ERROR_BAD_PARAMETERS;
binh = handle_put(&sys_ctx->db, handle);
if (!binh)
return TEE_ERROR_BAD_PARAMETERS;
if (binh->offs_bytes < binh->size_bytes)
res = binh->op->read(binh->h, NULL,
binh->size_bytes - binh->offs_bytes);
bin_close(binh);
if (handle_db_is_empty(&sys_ctx->db)) {
handle_db_destroy(&sys_ctx->db, bin_close);
free(sys_ctx);
sess->user_ctx = NULL;
}
return res;
}
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) {
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;
}
TEE_Result ldelf_syscall_map_bin(vaddr_t *va, size_t num_bytes,
unsigned long handle, size_t offs_bytes,
size_t pad_begin, size_t pad_end,
unsigned long flags)
{
TEE_Result res = TEE_SUCCESS;
struct ts_session *sess = ts_get_current_session();
struct user_mode_ctx *uctx = to_user_mode_ctx(sess->ctx);
struct system_ctx *sys_ctx = sess->user_ctx;
struct bin_handle *binh = NULL;
uint32_t num_rounded_bytes = 0;
struct file_slice *fs = NULL;
bool file_is_locked = false;
struct mobj *mobj = NULL;
uint32_t offs_pages = 0;
size_t num_pages = 0;
uint32_t prot = 0;
const uint32_t accept_flags = LDELF_MAP_FLAG_SHAREABLE |
LDELF_MAP_FLAG_WRITEABLE |
LDELF_MAP_FLAG_BTI |
LDELF_MAP_FLAG_EXECUTABLE;
if (!sys_ctx)
return TEE_ERROR_BAD_PARAMETERS;
binh = handle_lookup(&sys_ctx->db, handle);
if (!binh)
return TEE_ERROR_BAD_PARAMETERS;
if ((flags & accept_flags) != flags)
return TEE_ERROR_BAD_PARAMETERS;
if ((flags & LDELF_MAP_FLAG_SHAREABLE) &&
(flags & LDELF_MAP_FLAG_WRITEABLE))
return TEE_ERROR_BAD_PARAMETERS;
if ((flags & LDELF_MAP_FLAG_EXECUTABLE) &&
(flags & LDELF_MAP_FLAG_WRITEABLE))
return TEE_ERROR_BAD_PARAMETERS;
if (offs_bytes & SMALL_PAGE_MASK)
return TEE_ERROR_BAD_PARAMETERS;
prot = TEE_MATTR_UR | TEE_MATTR_PR;
if (flags & LDELF_MAP_FLAG_WRITEABLE)
prot |= TEE_MATTR_UW | TEE_MATTR_PW;
if (flags & LDELF_MAP_FLAG_EXECUTABLE)
prot |= TEE_MATTR_UX;
if (flags & LDELF_MAP_FLAG_BTI)
prot |= TEE_MATTR_GUARDED;
offs_pages = offs_bytes >> SMALL_PAGE_SHIFT;
if (ROUNDUP_OVERFLOW(num_bytes, SMALL_PAGE_SIZE, &num_rounded_bytes))
return TEE_ERROR_BAD_PARAMETERS;
num_pages = num_rounded_bytes / SMALL_PAGE_SIZE;
if (!file_trylock(binh->f)) {
* Before we can block on the file lock we must make all
* our page tables available for reclaiming in order to
* avoid a dead-lock with the other thread (which already
* is holding the file lock) mapping lots of memory below.
*/
vm_set_ctx(NULL);
file_lock(binh->f);
vm_set_ctx(uctx->ts_ctx);
}
file_is_locked = true;
fs = file_find_slice(binh->f, offs_pages);
if (fs) {
if (fs->page_offset != offs_pages ||
num_pages > fs->fobj->num_pages) {
res = TEE_ERROR_BAD_PARAMETERS;
goto err;
}
if (!(flags & LDELF_MAP_FLAG_SHAREABLE)) {
res = TEE_ERROR_BAD_PARAMETERS;
goto err;
}
mobj = mobj_with_fobj_alloc(fs->fobj, binh->f,
TEE_MATTR_MEM_TYPE_TAGGED);
if (!mobj) {
res = TEE_ERROR_OUT_OF_MEMORY;
goto err;
}
res = vm_map_pad(uctx, va, num_rounded_bytes,
prot, VM_FLAG_READONLY,
mobj, 0, pad_begin, pad_end, 0);
mobj_put(mobj);
if (res)
goto err;
} else {
struct fobj *f = fobj_ta_mem_alloc(num_pages);
struct file *file = NULL;
uint32_t vm_flags = 0;
if (!f) {
res = TEE_ERROR_OUT_OF_MEMORY;
goto err;
}
if (!(flags & LDELF_MAP_FLAG_WRITEABLE)) {
file = binh->f;
vm_flags |= VM_FLAG_READONLY;
}
mobj = mobj_with_fobj_alloc(f, file, TEE_MATTR_MEM_TYPE_TAGGED);
fobj_put(f);
if (!mobj) {
res = TEE_ERROR_OUT_OF_MEMORY;
goto err;
}
res = vm_map_pad(uctx, va, num_rounded_bytes,
TEE_MATTR_PRW, vm_flags, mobj, 0,
pad_begin, pad_end, 0);
mobj_put(mobj);
if (res)
goto err;
res = binh_copy_to(binh, *va, offs_bytes, num_bytes);
if (res)
goto err_unmap_va;
res = vm_set_prot(uctx, *va, num_rounded_bytes,
prot);
if (res)
goto err_unmap_va;
* The context currently is active set it again to update
* the mapping.
*/
vm_set_ctx(uctx->ts_ctx);
if (!(flags & LDELF_MAP_FLAG_WRITEABLE)) {
res = file_add_slice(binh->f, f, offs_pages);
if (res)
goto err_unmap_va;
}
}
file_unlock(binh->f);
return TEE_SUCCESS;
err_unmap_va:
if (vm_unmap(uctx, *va, num_rounded_bytes))
panic();
* The context currently is active set it again to update
* the mapping.
*/
vm_set_ctx(uctx->ts_ctx);
err:
if (file_is_locked)
file_unlock(binh->f);
return res;
}
TEE_Result ldelf_syscall_copy_from_bin(void *dst, size_t offs, size_t num_bytes,
unsigned long handle)
{
TEE_Result res = TEE_SUCCESS;
struct ts_session *sess = ts_get_current_session();
struct user_mode_ctx *uctx = to_user_mode_ctx(sess->ctx);
struct system_ctx *sys_ctx = sess->user_ctx;
struct bin_handle *binh = NULL;
res = vm_check_access_rights(uctx,
TEE_MEMORY_ACCESS_WRITE |
TEE_MEMORY_ACCESS_ANY_OWNER,
(uaddr_t)dst, num_bytes);
if (res)
return res;
if (!sys_ctx)
return TEE_ERROR_BAD_PARAMETERS;
binh = handle_lookup(&sys_ctx->db, handle);
if (!binh)
return TEE_ERROR_BAD_PARAMETERS;
return binh_copy_to(binh, (vaddr_t)dst, offs, num_bytes);
}
TEE_Result ldelf_syscall_set_prot(unsigned long va, size_t num_bytes,
unsigned long flags)
{
TEE_Result res = TEE_SUCCESS;
struct ts_session *sess = ts_get_current_session();
struct user_mode_ctx *uctx = to_user_mode_ctx(sess->ctx);
size_t sz = ROUNDUP(num_bytes, SMALL_PAGE_SIZE);
uint32_t prot = TEE_MATTR_UR | TEE_MATTR_PR;
uint32_t vm_flags = 0;
vaddr_t end_va = 0;
const uint32_t accept_flags = LDELF_MAP_FLAG_WRITEABLE |
LDELF_MAP_FLAG_BTI |
LDELF_MAP_FLAG_EXECUTABLE;
if ((flags & accept_flags) != flags)
return TEE_ERROR_BAD_PARAMETERS;
if (flags & LDELF_MAP_FLAG_WRITEABLE)
prot |= TEE_MATTR_UW | TEE_MATTR_PW;
if (flags & LDELF_MAP_FLAG_EXECUTABLE)
prot |= TEE_MATTR_UX;
if (flags & LDELF_MAP_FLAG_BTI)
prot |= TEE_MATTR_GUARDED;
* The vm_get_flags() and vm_unmap() are supposed to detect or handle
* overflow directly or indirectly. However, since this function is an
* API function it's worth having an extra guard here. If nothing else,
* to increase code clarity.
*/
if (ADD_OVERFLOW(va, sz, &end_va))
return TEE_ERROR_BAD_PARAMETERS;
res = vm_get_flags(uctx, va, sz, &vm_flags);
if (res)
return res;
if (vm_flags & VM_FLAG_PERMANENT)
return TEE_ERROR_ACCESS_DENIED;
* If the segment is a mapping of a part of a file (vm_flags &
* VM_FLAG_READONLY) it cannot be made writeable as all mapped
* files are mapped read-only.
*/
if ((vm_flags & VM_FLAG_READONLY) &&
(prot & (TEE_MATTR_UW | TEE_MATTR_PW)))
return TEE_ERROR_ACCESS_DENIED;
return vm_set_prot(uctx, va, sz, prot);
}
TEE_Result ldelf_syscall_remap(unsigned long old_va, vaddr_t *new_va,
size_t num_bytes, size_t pad_begin,
size_t pad_end)
{
TEE_Result res = TEE_SUCCESS;
struct ts_session *sess = ts_get_current_session();
struct user_mode_ctx *uctx = to_user_mode_ctx(sess->ctx);
uint32_t vm_flags = 0;
res = vm_get_flags(uctx, old_va, num_bytes, &vm_flags);
if (res)
return res;
if (vm_flags & VM_FLAG_PERMANENT)
return TEE_ERROR_ACCESS_DENIED;
res = vm_remap(uctx, new_va, old_va, num_bytes, pad_begin, pad_end);
return res;
}
TEE_Result ldelf_syscall_gen_rnd_num(void *buf, size_t num_bytes)
{
TEE_Result res = TEE_SUCCESS;
struct ts_session *sess = ts_get_current_session();
struct user_mode_ctx *uctx = to_user_mode_ctx(sess->ctx);
res = vm_check_access_rights(uctx,
TEE_MEMORY_ACCESS_WRITE |
TEE_MEMORY_ACCESS_ANY_OWNER,
(uaddr_t)buf, num_bytes);
if (res)
return res;
return crypto_rng_read(buf, num_bytes);
}
* Should be called after returning from ldelf. If user_ctx is not NULL means
* that ldelf crashed or otherwise didn't complete properly. This function will
* close the remaining handles and free the context structs allocated by ldelf.
*/
void ldelf_sess_cleanup(struct ts_session *sess)
{
struct system_ctx *sys_ctx = sess->user_ctx;
if (sys_ctx) {
handle_db_destroy(&sys_ctx->db, bin_close);
free(sys_ctx);
sess->user_ctx = NULL;
}
}