| /* |
| * Copyright (c) 2016, Linaro Limited |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <err.h> |
| #include <fcntl.h> |
| #include <pta_invoke_tests.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| #include <tee_client_api.h> |
| #include <tee_client_api_extensions.h> |
| #include <unistd.h> |
| |
| #include "crypto_common.h" |
| #include "sdp_basic.h" |
| #include "xtest_test.h" |
| |
| /* |
| * SDP basic test setup overview. |
| * |
| * - A dedicated trusted application (SDP basic TA) supports 3 commands: |
| * - 'inject' data from a nonsecure buffer into a secure buffer |
| * - 'transform' data inside a secure buffer (bitwise invert + unsigned incr) |
| * - 'dump' data from a secure buffer into a nonsecure buffer |
| |
| * - This test client application (CA) invokes the TA for these 3 operations, |
| * inject random value, trasforming them then dump them. |
| * |
| * To do so, CA allocates a 'SDP secure buffer' and invoke the TA for these 3 |
| * operations (inject then transform then dump) over the allocate buffer. |
| * |
| * The secure buffer is currently allocation through ION support adn |
| * registered to OP-TEE and as shared memory. |
| * |
| * To enhance test coverage against buffer alignement usecase, the CA invokes |
| * the TA with a variable offset inside the buffer. As CA injects random data |
| * into the buffer, the CA uses one of the random bytes to set the value of the |
| * offset in the accessed secure buffer. |
| * |
| * For debugging support, the CA may map (in nonsecure world) the secure |
| * buffer to read its content. As this is unsafe on a hardened platform, this |
| * operation is default disable. When enable, error only print out a warning |
| * trace but does not actually fail the test. This also give an easy way to |
| * check that some HW complains on access violation when nonsecure accesses |
| * secure data. |
| */ |
| |
| static int verbosity = 1; |
| |
| struct tee_ctx { |
| TEEC_Context ctx; |
| TEEC_Session sess; |
| }; |
| |
| int allocate_ion_buffer(size_t size, int heap_id) |
| { |
| struct ion_allocation_data alloc_data; |
| struct ion_handle_data hdl_data; |
| struct ion_fd_data fd_data; |
| int ion; |
| int fd = -1; |
| |
| ion = open("/dev/ion", O_RDWR); |
| if (ion < 0) { |
| fprintf(stderr, "Error; failed to open /dev/ion\n"); |
| verbose("Seems no ION heap is available.\n"); |
| verbose("To test ION allocation you can enable\n"); |
| verbose("CONFIG_ION and CONFIG_ION_DUMMY in your\n"); |
| verbose("linux kernel configuration.\n"); |
| return fd; |
| } |
| |
| if (heap_id < 0) |
| heap_id = DEFAULT_ION_HEAP_TYPE; |
| |
| verbose("Allocate in ION heap '%s'\n", |
| heap_id == ION_HEAP_TYPE_SYSTEM ? "system" : |
| heap_id == ION_HEAP_TYPE_SYSTEM_CONTIG ? "system contig" : |
| heap_id == ION_HEAP_TYPE_CARVEOUT ? "carveout" : |
| heap_id == ION_HEAP_TYPE_CHUNK ? "chunk" : |
| heap_id == ION_HEAP_TYPE_DMA ? "dma" : |
| heap_id == ION_HEAP_TYPE_UNMAPPED ? "unmapped" : |
| "custom"); |
| |
| alloc_data.len = size; |
| alloc_data.align = 0; |
| alloc_data.flags = 0; |
| alloc_data.heap_id_mask = 1 << heap_id; |
| if (ioctl(ion, ION_IOC_ALLOC, &alloc_data) == -1) |
| goto out; |
| |
| fd_data.handle = alloc_data.handle; |
| if (ioctl(ion, ION_IOC_SHARE, &fd_data) != -1) |
| fd = fd_data.fd; |
| |
| hdl_data.handle = alloc_data.handle; |
| (void)ioctl(ion, ION_IOC_FREE, &hdl_data); |
| out: |
| close(ion); |
| return fd; |
| } |
| |
| static void finalize_tee_ctx(struct tee_ctx *ctx) |
| { |
| if (!ctx) |
| return; |
| |
| TEEC_CloseSession(&ctx->sess); |
| TEEC_FinalizeContext(&ctx->ctx); |
| } |
| |
| static int create_tee_ctx(struct tee_ctx *ctx, enum test_target_ta target_ta) |
| { |
| TEEC_Result teerc; |
| TEEC_UUID *uuid; |
| uint32_t err_origin; |
| |
| switch (target_ta) { |
| case TEST_NS_TO_TA: |
| case TEST_TA_TO_TA: |
| case TEST_TA_TO_PTA: |
| uuid = &sdp_basic_ta_uuid; |
| break; |
| case TEST_NS_TO_PTA: |
| uuid = &pta_invoke_tests_ta_uuid; |
| break; |
| default: |
| return -1; |
| } |
| |
| teerc = TEEC_InitializeContext(NULL, &ctx->ctx); |
| if (teerc != TEEC_SUCCESS) |
| return -1; |
| |
| teerc = TEEC_OpenSession(&ctx->ctx, &ctx->sess, uuid, |
| TEEC_LOGIN_PUBLIC, NULL, NULL, &err_origin); |
| if (teerc != TEEC_SUCCESS) |
| fprintf(stderr, "Error: open session to target test %s failed %x %d\n", |
| (target_ta == TEST_NS_TO_PTA) ? "pTA" : "TA", |
| teerc, err_origin); |
| |
| return (teerc == TEEC_SUCCESS) ? 0 : -1; |
| } |
| |
| static int tee_register_buffer(struct tee_ctx *ctx, void **shm_ref, int fd) |
| { |
| TEEC_Result teerc; |
| TEEC_SharedMemory *shm; |
| |
| shm = malloc(sizeof(*shm)); |
| if (!shm) |
| return 1; |
| |
| shm->flags = TEEC_MEM_INPUT | TEEC_MEM_OUTPUT; |
| teerc = TEEC_RegisterSharedMemoryFileDescriptor(&ctx->ctx, shm, fd); |
| if (teerc != TEEC_SUCCESS) { |
| fprintf(stderr, "Error: TEEC_RegisterMemoryFileDescriptor() failed %x\n", |
| teerc); |
| return 1; |
| } |
| |
| *shm_ref = shm; |
| return 0; |
| } |
| |
| static void tee_deregister_buffer(struct tee_ctx *ctx, void *shm_ref) |
| { |
| (void)ctx; |
| |
| if (!shm_ref) |
| return; |
| |
| TEEC_ReleaseSharedMemory((TEEC_SharedMemory *)shm_ref); |
| free(shm_ref); |
| } |
| |
| static int inject_sdp_data(struct tee_ctx *ctx, |
| void *in, size_t offset, size_t len, void *shm_ref, int ind) |
| { |
| TEEC_SharedMemory *shm = (TEEC_SharedMemory *)shm_ref; |
| TEEC_Result teerc; |
| TEEC_Operation op; |
| uint32_t err_origin; |
| unsigned cmd; |
| |
| switch (ind) { |
| case TEST_NS_TO_TA: |
| cmd = TA_SDP_BASIC_CMD_INJECT; |
| break; |
| case TEST_TA_TO_TA: |
| cmd = TA_SDP_BASIC_CMD_INVOKE_INJECT; |
| break; |
| case TEST_TA_TO_PTA: |
| cmd = TA_SDP_BASIC_CMD_PTA_INJECT; |
| break; |
| case TEST_NS_TO_PTA: |
| cmd = PTA_INVOKE_TESTS_CMD_COPY_NSEC_TO_SEC; |
| break; |
| default: |
| return -1; |
| } |
| |
| memset(&op, 0, sizeof(op)); |
| op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, |
| TEEC_MEMREF_PARTIAL_OUTPUT, |
| TEEC_NONE, TEEC_NONE); |
| |
| op.params[0].tmpref.buffer = in; |
| op.params[0].tmpref.size = len; |
| |
| op.params[1].memref.parent = shm; |
| op.params[1].memref.size = len; |
| op.params[1].memref.offset = offset; |
| |
| teerc = TEEC_InvokeCommand(&ctx->sess, cmd, &op, &err_origin); |
| if (teerc != TEEC_SUCCESS) |
| fprintf(stderr, "Error: invoke SDP test TA (inject) failed %x %d\n", |
| teerc, err_origin); |
| |
| return (teerc == TEEC_SUCCESS) ? 0 : -1; |
| } |
| |
| static int transform_sdp_data(struct tee_ctx *ctx, |
| size_t offset, size_t len, void *shm_ref, int ind) |
| { |
| TEEC_SharedMemory *shm = (TEEC_SharedMemory *)shm_ref; |
| TEEC_Result teerc; |
| TEEC_Operation op; |
| uint32_t err_origin; |
| unsigned cmd; |
| |
| switch (ind) { |
| case TEST_NS_TO_TA: |
| cmd = TA_SDP_BASIC_CMD_TRANSFORM; |
| break; |
| case TEST_TA_TO_TA: |
| cmd = TA_SDP_BASIC_CMD_INVOKE_TRANSFORM; |
| break; |
| case TEST_TA_TO_PTA: |
| cmd = TA_SDP_BASIC_CMD_PTA_TRANSFORM; |
| break; |
| case TEST_NS_TO_PTA: |
| cmd = PTA_INVOKE_TESTS_CMD_READ_MODIFY_SEC; |
| break; |
| default: |
| return -1; |
| } |
| |
| memset(&op, 0, sizeof(op)); |
| op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_PARTIAL_INOUT, |
| TEEC_NONE, TEEC_NONE, TEEC_NONE); |
| op.params[0].memref.parent = shm; |
| op.params[0].memref.size = len; |
| op.params[0].memref.offset = offset; |
| |
| teerc = TEEC_InvokeCommand(&ctx->sess, cmd, &op, &err_origin); |
| if (teerc != TEEC_SUCCESS) |
| fprintf(stderr, "Error: invoke SDP test TA (transform) failed %x %d\n", |
| teerc, err_origin); |
| |
| return (teerc == TEEC_SUCCESS) ? 0 : -1; |
| } |
| |
| static int dump_sdp_data(struct tee_ctx *ctx, |
| void *out, size_t offset, size_t len, void *shm_ref, int ind) |
| { |
| TEEC_SharedMemory *shm = (TEEC_SharedMemory *)shm_ref; |
| TEEC_Result teerc; |
| TEEC_Operation op; |
| uint32_t err_origin; |
| unsigned cmd; |
| |
| switch (ind) { |
| case TEST_NS_TO_TA: |
| cmd = TA_SDP_BASIC_CMD_DUMP; |
| break; |
| case TEST_TA_TO_TA: |
| cmd = TA_SDP_BASIC_CMD_INVOKE_DUMP; |
| break; |
| case TEST_TA_TO_PTA: |
| cmd = TA_SDP_BASIC_CMD_PTA_DUMP; |
| break; |
| case TEST_NS_TO_PTA: |
| cmd = PTA_INVOKE_TESTS_CMD_COPY_SEC_TO_NSEC; |
| break; |
| default: |
| return -1; |
| } |
| |
| memset(&op, 0, sizeof(op)); |
| op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_PARTIAL_INPUT, |
| TEEC_MEMREF_TEMP_OUTPUT, |
| TEEC_NONE, TEEC_NONE); |
| op.params[0].memref.parent = shm; |
| op.params[0].memref.size = len; |
| op.params[0].memref.offset = offset; |
| |
| op.params[1].tmpref.buffer = out; |
| op.params[1].tmpref.size = len; |
| |
| teerc = TEEC_InvokeCommand(&ctx->sess, cmd, &op, &err_origin); |
| if (teerc != TEEC_SUCCESS) |
| fprintf(stderr, "Error: invoke SDP test TA (dump) failed %x %d\n", |
| teerc, err_origin); |
| |
| return (teerc == TEEC_SUCCESS) ? 0 : -1; |
| } |
| |
| static int check_sdp_dumped(struct tee_ctx *ctx, void *ref, size_t len, |
| void *out) |
| { |
| char *bref = (char *)ref; |
| char *data = (char *)out; |
| int err = 0; |
| |
| (void)ctx; |
| |
| while(len--) |
| if (*data++ != (unsigned char)(~(*bref++) + 1)) |
| err++; |
| |
| return err; |
| } |
| |
| /* |
| * Consider 32kByte + 1 of random data is sufficient for an accurate test |
| * whatever the test buffer size is. Random buffer is read as a ring buffer. |
| */ |
| #define RANDOM_BUFFER_SIZE (32 * 1024 + 1) |
| static int get_random_bytes(char *out, size_t len) |
| { |
| static char *rand_buf = NULL; |
| static size_t rand_idx = 0; |
| int rc; |
| |
| if (!rand_buf) { |
| const char rand_dev[] = "/dev/urandom"; |
| int fd; |
| |
| rand_buf = malloc(RANDOM_BUFFER_SIZE); |
| if (!rand_buf) { |
| fprintf(stderr, "failed to random buffer memory (%d bytes)\n", |
| RANDOM_BUFFER_SIZE); |
| return -1; |
| } |
| |
| fd = open(rand_dev, O_RDONLY); |
| if (fd < 0) { |
| fprintf(stderr, "failed to open %s\n", rand_dev); |
| return -1; |
| } |
| |
| rc = read(fd, rand_buf, RANDOM_BUFFER_SIZE); |
| if (rc != RANDOM_BUFFER_SIZE) { |
| fprintf(stderr, "failed to read %d bytes from %s\n", |
| RANDOM_BUFFER_SIZE, rand_dev); |
| return -1; |
| } |
| close(fd); |
| } |
| |
| while (len) { |
| size_t t_len = (RANDOM_BUFFER_SIZE < len) ? RANDOM_BUFFER_SIZE : len; |
| |
| if ((rand_idx + t_len) > RANDOM_BUFFER_SIZE) { |
| int sz_end = RANDOM_BUFFER_SIZE - rand_idx; |
| int sz_beg = t_len - sz_end; |
| |
| memcpy(out, rand_buf + rand_idx, sz_end); |
| memcpy(out + sz_end, rand_buf , sz_beg); |
| rand_idx = sz_beg; |
| } else { |
| memcpy(out, rand_buf + rand_idx, t_len); |
| rand_idx += t_len; |
| } |
| len -= t_len; |
| } |
| return 0; |
| } |
| |
| |
| int sdp_basic_test(enum test_target_ta ta, size_t size, size_t loop, |
| int ion_heap, int rnd_offset) |
| { |
| struct tee_ctx *ctx = NULL; |
| unsigned char *test_buf = NULL; |
| unsigned char *ref_buf = NULL; |
| void *shm_ref = NULL; |
| unsigned int err = 1; |
| int fd = -1; |
| size_t sdp_size = size; |
| size_t offset; |
| size_t loop_cnt; |
| |
| if (!loop) { |
| fprintf(stderr, "Error: null loop value\n"); |
| return 1; |
| } |
| |
| /* reduce size to enable offset tests (max offset is 255 bytes) */ |
| if (rnd_offset) |
| size -= 255; |
| |
| test_buf = malloc(size); |
| ref_buf = malloc(size); |
| if (!test_buf || !ref_buf) { |
| verbose("failed to allocate memory\n"); |
| goto out; |
| } |
| |
| fd = allocate_ion_buffer(sdp_size, ion_heap); |
| if (fd < 0) { |
| verbose("Failed to allocate SDP buffer (%lu bytes) in ION heap %d: %d\n", |
| sdp_size, ion_heap, fd); |
| goto out; |
| } |
| |
| /* register secure buffer to TEE */ |
| ctx = malloc(sizeof(*ctx)); |
| if (!ctx) |
| goto out; |
| if (create_tee_ctx(ctx, ta)) |
| goto out; |
| if (tee_register_buffer(ctx, &shm_ref, fd)) |
| goto out; |
| |
| /* release registered fd: tee should still hold refcount on resource */ |
| close(fd); |
| fd = -1; |
| |
| /* invoke trusted application with secure buffer as memref parameter */ |
| for (loop_cnt = loop; loop_cnt; loop_cnt--) { |
| /* get an buffer of random-like values */ |
| if (get_random_bytes((char *)ref_buf, size)) |
| goto out; |
| memcpy(test_buf, ref_buf, size); |
| /* random offset [0 255] */ |
| offset = (unsigned int)*ref_buf; |
| |
| /* TA writes into SDP buffer */ |
| if (inject_sdp_data(ctx, test_buf, offset, size, shm_ref, ta)) |
| goto out; |
| |
| /* TA reads/writes into SDP buffer */ |
| if (transform_sdp_data(ctx, offset, size, shm_ref, ta)) |
| goto out; |
| |
| /* TA reads into SDP buffer */ |
| if (dump_sdp_data(ctx, test_buf, offset, size, shm_ref, ta)) |
| goto out; |
| |
| /* check dumped data are the expected ones */ |
| if (check_sdp_dumped(ctx, ref_buf, size, test_buf)) { |
| fprintf(stderr, "check SDP data: %d errors\n", err); |
| goto out; |
| } |
| } |
| |
| err = 0; |
| verbose("%s: successed\n", __func__); |
| out: |
| if (err) |
| verbose("test failed\n"); |
| if (fd >= 0) |
| close(fd); |
| if (shm_ref) |
| tee_deregister_buffer(ctx, shm_ref); |
| finalize_tee_ctx(ctx); |
| free(ctx); |
| free(ref_buf); |
| free(test_buf); |
| return err; |
| } |
| |
| #define _TO_STR(x) #x |
| #define TO_STR(x) _TO_STR(x) |
| |
| static void usage(const char *progname, size_t size, int loop, int ion_heap) |
| { |
| fprintf(stderr, "Usage: %s [OPTION]\n", progname); |
| fprintf(stderr, |
| "Testing basic accesses to secure buffer (SDP) on OP-TEE.\n" |
| "Allocates a secure buffer and invoke a TA to access it.\n" |
| "TA is used to init/transform/dump the secure buffer.\n" |
| "CA check dumped content.\n\n"); |
| |
| fprintf(stderr, "Options:\n"); |
| fprintf(stderr, " -h|--help Print this help and exit\n"); |
| fprintf(stderr, " -v Be verbose\n"); |
| fprintf(stderr, " -s SIZE SDP buffer byte size [%zu]\n", size); |
| fprintf(stderr, " -n LOOP Test loop iterations [%u]\n", loop); |
| fprintf(stderr, " --ion-heap ID Target ION heap ID [%d]\n", ion_heap); |
| fprintf(stderr, " --no-offset No random offset [0 255] in buffer\n"); |
| } |
| |
| #define NEXT_ARG(i) \ |
| do { \ |
| if (++i == argc) { \ |
| fprintf(stderr, "%s: %s: missing argument\n", \ |
| argv[0], argv[i-1]); \ |
| return 1; \ |
| } \ |
| } while (0); |
| |
| int sdp_basic_runner_cmd_parser(int argc, char *argv[]) |
| { |
| size_t test_size = 5000; |
| size_t test_loop = 1000; |
| int ion_heap = DEFAULT_ION_HEAP_TYPE; |
| int rnd_offset = 1; |
| int i; |
| |
| /* Parse command line */ |
| for (i = 1; i < argc; i++) { |
| if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { |
| usage(argv[0], test_size, test_loop, ion_heap); |
| return 0; |
| } |
| } |
| for (i = 1; i < argc; i++) { |
| if (!strcmp(argv[i], "-v")) { |
| verbosity++; |
| } else if (!strcmp(argv[i], "-s")) { |
| NEXT_ARG(i); |
| test_size = atoi(argv[i]); |
| } else if (!strcmp(argv[i], "-n")) { |
| NEXT_ARG(i); |
| test_loop = atoi(argv[i]); |
| } else if (!strcmp(argv[i], "--ion-heap")) { |
| NEXT_ARG(i); |
| ion_heap = atoi(argv[i]); |
| } else if (!strcmp(argv[i], "--no-offset")) { |
| rnd_offset = 0; |
| } else { |
| fprintf(stderr, "%s: invalid argument: %s\n", |
| argv[0], argv[i]); |
| usage(argv[0], test_size, test_loop, ion_heap); |
| return 1; |
| } |
| } |
| |
| verbose("\nSecure Data Path basic accesses: NS invokes SDP TA\n"); |
| if (sdp_basic_test(TEST_NS_TO_TA, |
| test_size, test_loop, ion_heap, rnd_offset)) |
| return 1; |
| |
| verbose("\nSecure Data Path basic accesses: SDP TA invokes SDP TA\n"); |
| if (sdp_basic_test(TEST_TA_TO_TA, |
| test_size, test_loop, ion_heap, rnd_offset)) |
| return 1; |
| |
| verbose("\nSecure Data Path basic accesses: SDP TA invokes SDP pTA\n"); |
| if (sdp_basic_test(TEST_TA_TO_PTA, |
| test_size, test_loop, ion_heap, rnd_offset)) |
| return 1; |
| |
| verbose("\nSecure Data Path basic accesses: NS invokes SDP pTA (shall fail)\n"); |
| if (sdp_basic_test(TEST_NS_TO_PTA, |
| test_size, test_loop, ion_heap, rnd_offset)) |
| verbose("-> false negative: pTAs refuse SDP memref from NS clients.\n"); |
| |
| return 0; |
| } |