[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH v4 07/10] xen/arm: optee: add support for arbitrary shared memory
Julien Grall writes: > On 07/03/2019 21:04, Volodymyr Babchuk wrote: >> From: Volodymyr Babchuk <vlad.babchuk@xxxxxxxxx> > > Some of the patches are using your EPAM e-mail addresss. Other are > using your gmail address. Could you confirm this is expected? No, I'll make sure that all patches are authored by <volodymyr_babchuk@xxxxxxxx> >> >> Shared memory is widely used by NW (Normal World) to communicate with >> TAs (Trusted Applications) in OP-TEE. NW can share part of own memory >> with TA or with OP-TEE core, by registering it in OP-TEE, or by >> providing a temporal reference. Anyways, information about such memory >> buffers are sent to OP-TEE as a list of pages. This mechanism is >> described in optee_msg.h. >> >> Mediator should step in when NW tries to share memory with >> OP-TEE for two reasons: >> >> 1. Do address translation from IPA to PA. >> 2. Pin domain pages while they are mapped into OP-TEE or TA >> address space, so domain can't transfer this pages to >> other domain or balloon out them. >> >> Address translation is done by translate_noncontig(...) function. >> It allocates new buffer from domheap and then walks on guest >> provided list of pages, translates addresses and stores PAs into >> newly allocated buffer. This buffer will be provided to OP-TEE >> instead of original buffer from the guest. This buffer will >> be freed at the end of standard call. >> >> In the same time this function pins pages and stores them in >> struct optee_shm_buf object. This object will live all the time, >> when given SHM buffer is known to OP-TEE. It will be freed >> after guest unregisters shared buffer. At this time pages >> will be unpinned. >> >> Guest can share buffer with OP-TEE for duration for one call, >> or permanently, using OPTEE_MSG_CMD_REGISTER_SHM call. We need >> to handle both options. >> >> Also we want to limit total size of shared buffers. As it is not >> possible to get limit from OP-TEE, we need to choose some arbitrary >> value. Currently limit is 16384 of 4K pages. > > I can't promise Xen will only be 4K only. So it would be better to > make the number agnostic. Or at least writing clearly on top of the > definition that it is assumed 4KB (maybe with a BUILD_BUG_ON(PAGE_SIZE > != 4096) if not already in place). I can replace define with something like (67108864 / PAGE_SIZE). With appropriate comment, of course. > >> >> Signed-off-by: Volodymyr Babchuk <vlad.babchuk@xxxxxxxxx> >> --- >> All the patches to optee.c should be merged together. They were >> split to ease up review. But they depend heavily on each other. >> >> Changes from v3: >> - Reworked pagelists storage - there is no more static storage for >> 5 buffers, instead structure with all data is allocated dynamically >> - Now this code uses domheap instead of xenheap >> - Various style fixes >> - gdprintk() fixes >> >> Changes from v2: >> - Made sure that guest does not tries to register shared buffer with >> the same cookie twice >> - Fixed coding style >> - Use access_guest_memory_by_ipa() instead of direct memory mapping >> --- >> xen/arch/arm/tee/optee.c | 404 +++++++++++++++++++++++++++++++++++++++ >> 1 file changed, 404 insertions(+) >> >> diff --git a/xen/arch/arm/tee/optee.c b/xen/arch/arm/tee/optee.c >> index 291ed2fe25..14e295a422 100644 >> --- a/xen/arch/arm/tee/optee.c >> +++ b/xen/arch/arm/tee/optee.c >> @@ -24,6 +24,22 @@ >> #include <asm/tee/optee_msg.h> >> #include <asm/tee/optee_smc.h> >> +/* >> + * "The return code is an error that originated within the underlying >> + * communications stack linking the rich OS with the TEE" as described >> + * in GP TEE Client API Specification. >> + */ >> +#define TEEC_ORIGIN_COMMS 0x00000002 >> + >> +/* >> + * "Input parameters were invalid" as described >> + * in GP TEE Client API Specification. >> + */ >> +#define TEEC_ERROR_BAD_PARAMETERS 0xFFFF0006 >> + >> +/* "System ran out of resources" */ >> +#define TEEC_ERROR_OUT_OF_MEMORY 0xFFFF000C >> + >> /* Client ID 0 is reserved for hypervisor itself */ >> #define OPTEE_CLIENT_ID(domain) ((domain)->domain_id + 1) >> @@ -33,6 +49,15 @@ >> */ >> #define DEF_MAX_OPTEE_THREADS 16 >> +/* >> + * Maximum total number of pages that guest can share with >> + * OP-TEE. Currently value is selected arbitrary. Actual number of >> + * pages depends on free heap in OP-TEE. As we can't do any >> + * assumptions about OP-TEE heap usage, we limit number of pages >> + * arbitrary. >> + */ >> +#define MAX_TOTAL_SMH_BUF_PG 16384 >> + >> #define OPTEE_KNOWN_NSEC_CAPS OPTEE_SMC_NSEC_CAP_UNIPROCESSOR >> #define OPTEE_KNOWN_SEC_CAPS (OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM | \ >> OPTEE_SMC_SEC_CAP_UNREGISTERED_SHM | \ >> @@ -65,11 +90,31 @@ struct shm_rpc { >> uint64_t cookie; >> }; >> +/* Shared memory buffer for arbitrary data */ >> +struct optee_shm_buf { >> + struct list_head list; >> + uint64_t cookie; >> + unsigned int page_cnt; >> + /* >> + * Shadowed container for list of pages that guest tries to share >> + * with OP-TEE. This is not the list of pages that guest shared >> + * with OP-TEE, but container for list of those pages. Check >> + * OPTEE_MSG_ATTR_NONCONTIG definition in optee_msg.h for more >> + * information. >> + */ >> + struct page_info *pg_list; >> + unsigned int pg_list_order; >> + /* Pinned guest pages that are shared with OP-TEE */ >> + struct page_info *pages[]; >> +}; >> + >> /* Domain context */ >> struct optee_domain { >> struct list_head call_list; >> struct list_head shm_rpc_list; >> + struct list_head optee_shm_buf_list; >> atomic_t call_count; >> + atomic_t optee_shm_buf_pages; >> spinlock_t lock; >> }; >> @@ -136,7 +181,9 @@ static int optee_domain_init(struct domain *d) >> INIT_LIST_HEAD(&ctx->call_list); >> INIT_LIST_HEAD(&ctx->shm_rpc_list); >> + INIT_LIST_HEAD(&ctx->optee_shm_buf_list); >> atomic_set(&ctx->call_count, 0); >> + atomic_set(&ctx->optee_shm_buf_pages, 0); >> spin_lock_init(&ctx->lock); >> d->arch.tee = ctx; >> @@ -363,10 +410,134 @@ static void free_shm_rpc(struct optee_domain *ctx, >> uint64_t cookie) >> xfree(shm_rpc); >> } >> +static struct optee_shm_buf *allocate_optee_shm_buf(struct >> optee_domain *ctx, >> + uint64_t cookie, >> + unsigned int pages_cnt, >> + struct page_info >> *pg_list, >> + unsigned int >> pg_list_order) >> +{ >> + struct optee_shm_buf *optee_shm_buf, *optee_shm_buf_tmp; >> + int old, new; >> + int err_code; >> + >> + do >> + { >> + old = atomic_read(&ctx->optee_shm_buf_pages); >> + new = old + pages_cnt; >> + if ( new >= MAX_TOTAL_SMH_BUF_PG ) > > Again, the limitation is in number of page and quite high. What would > prevent a guest to register shared memory page by page? If nothing, > then I think you can end up to interesting issues in Xen because of > the growing list and memory used. OP-TEE will limit this on it's side. In most cases Xen have much bigger heap than OP-TEE :-) Do you want me to limit this both in memory size and in number of buffers? > >> + return ERR_PTR(-ENOMEM); >> + } >> + while ( unlikely(old != atomic_cmpxchg(&ctx->optee_shm_buf_pages, >> + old, new)) ); >> + >> + optee_shm_buf = xzalloc_bytes(sizeof(struct optee_shm_buf) + >> + pages_cnt * sizeof(struct page *)); >> + if ( !optee_shm_buf ) >> + { >> + err_code = -ENOMEM; >> + goto err; >> + } >> + >> + optee_shm_buf->cookie = cookie; >> + optee_shm_buf->pg_list = pg_list; >> + optee_shm_buf->pg_list_order = pg_list_order; >> + >> + spin_lock(&ctx->lock); >> + /* Check if there is already SHM with the same cookie */ >> + list_for_each_entry( optee_shm_buf_tmp, &ctx->optee_shm_buf_list, list ) >> + { >> + if ( optee_shm_buf_tmp->cookie == cookie ) >> + { >> + spin_unlock(&ctx->lock); >> + gdprintk(XENLOG_WARNING, "Guest tries to use the same SHM >> buffer cookie %lx\n", > > The line looks too long. Please split after the first comma. Ah, okay. I seen both variant in the code and was unsure which one is right. Will do for all long messages. [...] >> static int optee_relinquish_resources(struct domain *d) >> { >> struct optee_std_call *call, *call_tmp; >> struct shm_rpc *shm_rpc, *shm_rpc_tmp; >> + struct optee_shm_buf *optee_shm_buf, *optee_shm_buf_tmp; >> struct optee_domain *ctx = d->arch.tee; >> if ( !ctx ) >> @@ -381,6 +552,13 @@ static int optee_relinquish_resources(struct domain *d) >> list_for_each_entry_safe( shm_rpc, shm_rpc_tmp, &ctx->shm_rpc_list, >> list ) >> free_shm_rpc(ctx, shm_rpc->cookie); >> + if ( hypercall_preempt_check() ) >> + return -ERESTART; > > Same question as the other hypercall_preempt_check(). Excuse me, but what question? I looked thru all your emails and can't find one. >> + >> + list_for_each_entry_safe( optee_shm_buf, optee_shm_buf_tmp, >> + &ctx->optee_shm_buf_list, list ) >> + free_optee_shm_buf(ctx, optee_shm_buf->cookie); >> + >> return 0; >> } >> @@ -406,11 +584,186 @@ static void optee_domain_destroy(struct >> domain *d) >> ASSERT(!spin_is_locked(&ctx->lock)); >> ASSERT(!atomic_read(&ctx->call_count)); >> + ASSERT(!atomic_read(&ctx->optee_shm_buf_pages)); >> ASSERT(list_empty(&ctx->shm_rpc_list)); >> XFREE(d->arch.tee); >> } >> +#define PAGELIST_ENTRIES_PER_PAGE \ >> + ((OPTEE_MSG_NONCONTIG_PAGE_SIZE / sizeof(u64)) - 1) >> + >> +static size_t get_pages_list_size(size_t num_entries) >> +{ >> + int pages = DIV_ROUND_UP(num_entries, PAGELIST_ENTRIES_PER_PAGE); >> + >> + return pages * OPTEE_MSG_NONCONTIG_PAGE_SIZE; >> +} >> + >> +static int translate_noncontig(struct optee_domain *ctx, >> + struct optee_std_call *call, >> + struct optee_msg_param *param) >> +{ >> + uint64_t size; >> + unsigned int page_offset; >> + unsigned int num_pages; >> + unsigned int order; >> + unsigned int entries_on_page = 0; >> + gfn_t gfn; >> + p2m_type_t p2m; >> + struct page_info *guest_page, *xen_pages; >> + struct optee_shm_buf *optee_shm_buf; >> + /* >> + * This is memory layout for page list. Basically list consists of 4k >> pages, >> + * every page store 511 page addresses of user buffer and page address >> of >> + * the next page of list. >> + * >> + * Refer to OPTEE_MSG_ATTR_NONCONTIG description in optee_msg.h for >> details. >> + */ >> + struct { >> + uint64_t pages_list[PAGELIST_ENTRIES_PER_PAGE]; >> + uint64_t next_page_data; >> + } *pages_data_guest, *pages_data_xen; >> + >> + /* Offset of user buffer withing page */ > > Offset of the buffer within the OP-TEE page > >> + page_offset = param->u.tmem.buf_ptr & (OPTEE_MSG_NONCONTIG_PAGE_SIZE - >> 1); >> + >> + /* Size of the user buffer in bytes */ >> + size = ROUNDUP(param->u.tmem.size + page_offset, >> + OPTEE_MSG_NONCONTIG_PAGE_SIZE); >> + >> + num_pages = DIV_ROUND_UP(size, OPTEE_MSG_NONCONTIG_PAGE_SIZE); >> + >> + order = get_order_from_bytes(get_pages_list_size(num_pages)); >> + >> + xen_pages = alloc_domheap_pages(current->domain, order, 0); >> + if ( !xen_pages ) >> + return -ENOMEM; >> + >> + optee_shm_buf = allocate_optee_shm_buf(ctx, param->u.tmem.shm_ref, >> + num_pages, xen_pages, order); > > In an earlier version, I pointed out that you will allow to allocate > up to 64MB per call. This is a potential security issue on Xen. While > I said I would be happy to get this code merged as it, I would expect > to at least see a TODO in the code explaining potential problem. So we > know the problem exist and can't security support until this is fixed. Sure. You want me to add this TODO there or inside allocate_optee_shm_buf() function, where it actually does allocation? > I may have missed other places while reviewing this version. Please go > back in the review I have made in the past and document all the > potential security holes. Okay, will do. >> + if ( IS_ERR(optee_shm_buf) ) >> + return PTR_ERR(optee_shm_buf); >> + >> + gfn = gaddr_to_gfn(param->u.tmem.buf_ptr & >> + ~(OPTEE_MSG_NONCONTIG_PAGE_SIZE - 1)); >> + >> + guest_page = get_page_from_gfn(current->domain, gfn_x(gfn), &p2m, >> P2M_ALLOC); >> + if ( !guest_page || p2m != p2m_ram_rw ) >> + return -EINVAL; >> + >> + pages_data_guest = __map_domain_page(guest_page); >> + pages_data_xen = __map_domain_page(xen_pages); >> + >> + while ( num_pages ) >> + { >> + struct page_info *page; > > Newline here please. > >> + page = get_page_from_gfn(current->domain, >> + >> paddr_to_pfn(pages_data_guest->pages_list[entries_on_page]), >> + &p2m, P2M_ALLOC); > > The indentation is wrong here. But the problem is due to the long > name. For instance, you have 3 times the word "page" on the same > line. Is that necessary? Code is quite complex. I want every variable to be as descriptive as possible. In some cases it leads to issues like this :-) I'll see what can be done there. >> + >> + if ( !page || p2m != p2m_ram_rw ) >> + goto err_unmap; >> + >> + optee_shm_buf->pages[optee_shm_buf->page_cnt++] = page; >> + pages_data_xen->pages_list[entries_on_page] = page_to_maddr(page); >> + entries_on_page++; >> + >> + if ( entries_on_page == PAGELIST_ENTRIES_PER_PAGE ) >> + { >> + pages_data_xen->next_page_data = page_to_maddr(xen_pages + 1); >> + unmap_domain_page(pages_data_xen); >> + xen_pages++; >> + >> + gfn = gaddr_to_gfn(pages_data_guest->next_page_data); >> + >> + unmap_domain_page(pages_data_guest); >> + put_page(guest_page); >> + >> + guest_page = get_page_from_gfn(current->domain, gfn_x(gfn), >> &p2m, >> + P2M_ALLOC); >> + if ( !guest_page || p2m != p2m_ram_rw ) >> + return -EINVAL; >> + >> + pages_data_guest = __map_domain_page(guest_page); >> + pages_data_xen = __map_domain_page(xen_pages); >> + /* Roll over to the next page */ >> + entries_on_page = 0; >> + } >> + num_pages--; >> + } >> + >> + unmap_domain_page(pages_data_guest); >> + unmap_domain_page(pages_data_xen); >> + put_page(guest_page); >> + >> + param->u.tmem.buf_ptr = page_to_maddr(optee_shm_buf->pg_list) | >> + page_offset; >> + >> + return 0; >> + >> +err_unmap: >> + unmap_domain_page(pages_data_guest); >> + unmap_domain_page(pages_data_xen); >> + put_page(guest_page); >> + free_optee_shm_buf(ctx, optee_shm_buf->cookie); >> + >> + return -EINVAL; >> +} >> + >> +static int translate_params(struct optee_domain *ctx, >> + struct optee_std_call *call) >> +{ >> + unsigned int i; >> + uint32_t attr; >> + int ret = 0; >> + >> + for ( i = 0; i < call->xen_arg->num_params; i++ ) >> + { >> + attr = call->xen_arg->params[i].attr; >> + >> + switch ( attr & OPTEE_MSG_ATTR_TYPE_MASK ) >> + { >> + case OPTEE_MSG_ATTR_TYPE_TMEM_INPUT: >> + case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT: >> + case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT: >> + if ( attr & OPTEE_MSG_ATTR_NONCONTIG ) >> + { >> + ret = translate_noncontig(ctx, call, call->xen_arg->params >> + i); >> + if ( ret ) >> + goto out; >> + } >> + else >> + { >> + gdprintk(XENLOG_WARNING, "Guest tries to use old tmem >> arg\n"); >> + ret = -EINVAL; >> + goto out; >> + } >> + break; >> + case OPTEE_MSG_ATTR_TYPE_NONE: >> + case OPTEE_MSG_ATTR_TYPE_VALUE_INPUT: >> + case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT: >> + case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT: >> + case OPTEE_MSG_ATTR_TYPE_RMEM_INPUT: >> + case OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT: >> + case OPTEE_MSG_ATTR_TYPE_RMEM_INOUT: >> + continue; >> + } >> + } >> + >> +out: >> + if ( ret ) >> + { >> + call->xen_arg->ret_origin = TEEC_ORIGIN_COMMS; >> + if ( ret == -ENOMEM ) >> + call->xen_arg->ret = TEEC_ERROR_OUT_OF_MEMORY; >> + else >> + call->xen_arg->ret = TEEC_ERROR_BAD_PARAMETERS; >> + } >> + >> + return ret; >> +} >> + >> /* >> * Copy command buffer into domheap memory to: >> * 1) Hide translated addresses from guest >> @@ -537,6 +890,27 @@ static void copy_std_request_back(struct optee_domain >> *ctx, >> put_page(page); >> } >> + >> +static void free_shm_buffers(struct optee_domain *ctx, >> + struct optee_msg_arg *arg) >> +{ >> + unsigned int i; >> + >> + for ( i = 0; i < arg->num_params; i ++ ) >> + { >> + switch ( arg->params[i].attr & OPTEE_MSG_ATTR_TYPE_MASK ) >> + { >> + case OPTEE_MSG_ATTR_TYPE_TMEM_INPUT: >> + case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT: >> + case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT: >> + free_optee_shm_buf(ctx, arg->params[i].u.tmem.shm_ref); >> + break; >> + default: >> + break; >> + } >> + } >> +} >> + >> /* Handle RPC return from OP-TEE */ >> static void handle_rpc_return(struct cpu_user_regs *regs, >> struct optee_std_call *call) >> @@ -556,6 +930,8 @@ static void handle_rpc_return(struct cpu_user_regs *regs, >> * If this is RPC - we need to store call context and return back to guest. >> * If call is complete - we need to return results with >> copy_std_request_back() >> * and then we will destroy the call context as it is not needed anymore. >> + * >> + * Shared buffers should be handled in a special way. >> */ >> static void execute_std_call(struct optee_domain *ctx, >> struct cpu_user_regs *regs, >> @@ -576,6 +952,23 @@ static void execute_std_call(struct optee_domain *ctx, >> copy_std_request_back(ctx, regs, call); >> + switch ( call->xen_arg->cmd ) >> + { >> + case OPTEE_MSG_CMD_REGISTER_SHM: >> + if ( call->xen_arg->ret == 0 ) >> + free_optee_shm_buf_pg_list(ctx, >> + call->xen_arg->params[0].u.tmem.shm_ref); >> + else >> + free_optee_shm_buf(ctx, >> call->xen_arg->params[0].u.tmem.shm_ref); >> + break; >> + case OPTEE_MSG_CMD_UNREGISTER_SHM: >> + if ( call->xen_arg->ret == 0 ) >> + free_optee_shm_buf(ctx, >> call->xen_arg->params[0].u.rmem.shm_ref); >> + break; >> + default: >> + free_shm_buffers(ctx, call->xen_arg); >> + } > > This switch seems to be new. Could you explain in a comment what they > are supposed to do? For instance, why do you need to make > UNREGISTER_SHM and REGISTER_SHM speific by specifying the exact > buffer? This is partially covered in comment above execute_std_call() function definition. Basically, there are cases when guest shares memory temporary, for duration of one call. And there is a case when guest registers shared memory buffer on OP-TEE, so then the guest can refer the buffer in subsequent calls. I'll put this in the comment. >> + >> put_std_call(ctx, call); >> free_std_call(ctx, call); >> } >> @@ -606,6 +999,17 @@ static void handle_std_call(struct optee_domain *ctx, >> case OPTEE_MSG_CMD_CANCEL: >> case OPTEE_MSG_CMD_REGISTER_SHM: >> case OPTEE_MSG_CMD_UNREGISTER_SHM: >> + if( translate_params(ctx, call) ) >> + { >> + /* >> + * translate_params() sets xen_arg->ret value to non-zero. >> + * So, technically, SMC was successful, but there was an error >> + * during handling standard call encapsulated into this SMC. >> + */ >> + copy_std_request_back(ctx, regs, call); >> + set_return(regs, OPTEE_SMC_RETURN_OK); >> + goto err; >> + } >> execute_std_call(ctx, regs, call); >> return; >> default: >> > > Cheers, -- Best regards,Volodymyr Babchuk _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxxxxxxxxx https://lists.xenproject.org/mailman/listinfo/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |