[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

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.