| /* |
| * Copyright (c) 2016-2017, 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 <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <netdb.h> |
| #include <netinet/in.h> |
| #include <optee_msg_supplicant.h> |
| #include <poll.h> |
| #include <pthread.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <sys/queue.h> |
| #include <sys/socket.h> |
| #include <tee_client_api.h> |
| #include <teec_trace.h> |
| #include <tee_socket.h> |
| #include <tee_supplicant.h> |
| #include <unistd.h> |
| |
| #include "handle.h" |
| #include "__tee_isocket_defines.h" |
| #include "__tee_ipsocket.h" |
| #include "__tee_tcpsocket_defines.h" |
| #include "__tee_tcpsocket_defines_extensions.h" |
| #include "__tee_udpsocket_defines.h" |
| |
| #ifndef __aligned |
| #define __aligned(x) __attribute__((__aligned__(x))) |
| #endif |
| #include <linux/tee.h> |
| |
| /* |
| * Used when checking how much data we have queued. |
| * |
| * For SOCK_DGRAM we try to be accurate up to 4096 bytes as |
| * that's our arbitrary chosen sensible upper size (with |
| * some margin). Larger size doesn't make much sense since |
| * anything larger than the MTU is bound to cause trouble |
| * on a congested network. |
| * |
| * For SOCK_STREAM we chose the same upper limit for |
| * simplicity. It doesn't matter if there's more queued, |
| * no data will be lost. |
| */ |
| #define SUPP_MAX_PEEK_LEN 4096 |
| |
| struct sock_instance { |
| uint32_t id; |
| struct handle_db db; |
| TAILQ_ENTRY(sock_instance) link; |
| }; |
| |
| static pthread_mutex_t sock_mutex = PTHREAD_MUTEX_INITIALIZER; |
| TAILQ_HEAD(, sock_instance) sock_instances = |
| TAILQ_HEAD_INITIALIZER(sock_instances); |
| |
| static void sock_lock(void) |
| { |
| pthread_mutex_lock(&sock_mutex); |
| } |
| |
| static void sock_unlock(void) |
| { |
| pthread_mutex_unlock(&sock_mutex); |
| } |
| |
| static struct sock_instance *sock_instance_find(uint32_t instance_id) |
| { |
| struct sock_instance *si = NULL; |
| |
| TAILQ_FOREACH(si, &sock_instances, link) { |
| if (si->id == instance_id) |
| return si; |
| } |
| return NULL; |
| } |
| |
| static void *fd_to_handle_ptr(int fd) |
| { |
| uintptr_t ptr = 0; |
| |
| assert(fd >= 0); |
| ptr = fd + 1; |
| return (void *)ptr; |
| } |
| |
| static int handle_ptr_to_fd(void *ptr) |
| { |
| assert(ptr); |
| return (uintptr_t)ptr - 1; |
| } |
| |
| static int sock_handle_get(uint32_t instance_id, int fd) |
| { |
| int handle = -1; |
| struct sock_instance *si = NULL; |
| |
| sock_lock(); |
| |
| si = sock_instance_find(instance_id); |
| if (!si) { |
| si = calloc(1, sizeof(*si)); |
| if (!si) |
| goto out; |
| si->id = instance_id; |
| TAILQ_INSERT_TAIL(&sock_instances, si, link); |
| } |
| |
| handle = handle_get(&si->db, fd_to_handle_ptr(fd)); |
| out: |
| sock_unlock(); |
| return handle; |
| } |
| |
| static int sock_handle_to_fd(uint32_t instance_id, uint32_t handle) |
| { |
| int fd = -1; |
| struct sock_instance *si = NULL; |
| |
| sock_lock(); |
| si = sock_instance_find(instance_id); |
| if (si) |
| fd = handle_ptr_to_fd(handle_lookup(&si->db, handle)); |
| sock_unlock(); |
| return fd; |
| } |
| |
| static void sock_handle_put(uint32_t instance_id, uint32_t handle) |
| { |
| struct sock_instance *si = NULL; |
| |
| sock_lock(); |
| si = sock_instance_find(instance_id); |
| if (si) |
| handle_put(&si->db, handle); |
| sock_unlock(); |
| } |
| |
| static bool chk_pt(struct tee_ioctl_param *param, uint32_t type) |
| { |
| return (param->attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) == type; |
| } |
| |
| static int fd_flags_add(int fd, int flags) |
| { |
| int val = 0; |
| |
| val = fcntl(fd, F_GETFD, 0); |
| if (val == -1) |
| return -1; |
| |
| val |= flags; |
| |
| return fcntl(fd, F_SETFL, val); |
| } |
| |
| static TEEC_Result sock_connect(uint32_t ip_vers, unsigned int protocol, |
| const char *server, uint16_t port, int *ret_fd) |
| { |
| TEEC_Result r = TEEC_ERROR_GENERIC; |
| struct addrinfo *res0 = NULL; |
| struct addrinfo *res = NULL; |
| int fd = -1; |
| char port_name[10] = { 0 }; |
| struct addrinfo hints; |
| |
| memset(&hints, 0, sizeof(hints)); |
| |
| snprintf(port_name, sizeof(port_name), "%" PRIu16, port); |
| |
| switch (ip_vers) { |
| case TEE_IP_VERSION_DC: |
| hints.ai_family = AF_UNSPEC; |
| break; |
| case TEE_IP_VERSION_4: |
| hints.ai_family = AF_INET; |
| break; |
| case TEE_IP_VERSION_6: |
| hints.ai_family = AF_INET6; |
| break; |
| default: |
| return TEEC_ERROR_BAD_PARAMETERS; |
| } |
| |
| if (protocol == TEE_ISOCKET_PROTOCOLID_TCP) |
| hints.ai_socktype = SOCK_STREAM; |
| else if (protocol == TEE_ISOCKET_PROTOCOLID_UDP) |
| hints.ai_socktype = SOCK_DGRAM; |
| else |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| if (getaddrinfo(server, port_name, &hints, &res0)) |
| return TEE_ISOCKET_ERROR_HOSTNAME; |
| |
| for (res = res0; res; res = res->ai_next) { |
| fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); |
| if (fd == -1) { |
| if (errno == ENOMEM || errno == ENOBUFS) |
| r = TEE_ISOCKET_ERROR_OUT_OF_RESOURCES; |
| else |
| r = TEEC_ERROR_GENERIC; |
| continue; |
| } |
| |
| if (connect(fd, res->ai_addr, res->ai_addrlen)) { |
| if (errno == ETIMEDOUT) |
| r = TEE_ISOCKET_ERROR_TIMEOUT; |
| else |
| r = TEEC_ERROR_COMMUNICATION; |
| |
| close(fd); |
| fd = -1; |
| continue; |
| } |
| |
| if (fd_flags_add(fd, O_NONBLOCK)) { |
| close(fd); |
| fd = -1; |
| r = TEEC_ERROR_GENERIC; |
| break; |
| } |
| |
| r = TEEC_SUCCESS; |
| break; |
| } |
| |
| freeaddrinfo(res0); |
| *ret_fd = fd; |
| return r; |
| } |
| |
| static TEEC_Result tee_socket_open(size_t num_params, |
| struct tee_ioctl_param *params) |
| { |
| TEEC_Result res = TEEC_ERROR_GENERIC; |
| int handle = 0; |
| int fd = 0; |
| uint32_t instance_id = 0; |
| char *server = NULL; |
| uint32_t ip_vers = 0; |
| uint16_t port = 0; |
| uint32_t protocol = 0; |
| |
| if (num_params != 4 || |
| !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) || |
| !chk_pt(params + 1, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) || |
| !chk_pt(params + 2, TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT) || |
| !chk_pt(params + 3, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| instance_id = params[0].b; |
| port = params[1].a; |
| protocol = params[1].b; |
| ip_vers = params[1].c; |
| |
| server = tee_supp_param_to_va(params + 2); |
| if (!server || server[MEMREF_SIZE(params + 2) - 1] != '\0') |
| return TEE_ISOCKET_ERROR_HOSTNAME; |
| |
| res = sock_connect(ip_vers, protocol, server, port, &fd); |
| if (res != TEEC_SUCCESS) |
| return res; |
| |
| handle = sock_handle_get(instance_id, fd); |
| if (handle < 0) { |
| close(fd); |
| return TEEC_ERROR_OUT_OF_MEMORY; |
| } |
| |
| params[3].a = handle; |
| return TEEC_SUCCESS; |
| } |
| |
| static TEEC_Result tee_socket_close(size_t num_params, |
| struct tee_ioctl_param *params) |
| { |
| int handle = 0; |
| uint32_t instance_id = 0; |
| int fd = 0; |
| |
| if (num_params != 1 || |
| !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| instance_id = params[0].b; |
| handle = params[0].c; |
| fd = sock_handle_to_fd(instance_id, handle); |
| if (fd < 0) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| sock_handle_put(instance_id, handle); |
| if (close(fd)) { |
| EMSG("tee_socket_close: close(%d): %s", fd, strerror(errno)); |
| return TEEC_ERROR_GENERIC; |
| } |
| return TEEC_SUCCESS; |
| } |
| |
| static void sock_close_cb(int handle, void *ptr, void *arg) |
| { |
| struct sock_instance *si = arg; |
| int fd = handle_ptr_to_fd(ptr); |
| |
| if (close(fd)) |
| EMSG("sock_close_cb instance_id %d handle %d fd %d: %s", |
| si->id, handle, fd, strerror(errno)); |
| } |
| |
| static TEEC_Result tee_socket_close_all(size_t num_params, |
| struct tee_ioctl_param *params) |
| { |
| uint32_t instance_id = 0; |
| struct sock_instance *si = NULL; |
| |
| if (num_params != 1 || |
| !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| instance_id = params[0].b; |
| sock_lock(); |
| si = sock_instance_find(instance_id); |
| if (si) |
| handle_foreach_put(&si->db, sock_close_cb, si); |
| sock_unlock(); |
| |
| return TEEC_SUCCESS; |
| } |
| |
| #define TS_NSEC_PER_SEC 1000000000 |
| |
| static void ts_add(const struct timespec *a, const struct timespec *b, |
| struct timespec *res) |
| { |
| res->tv_sec = a->tv_sec + b->tv_sec; |
| res->tv_nsec = a->tv_nsec + b->tv_nsec; |
| if (res->tv_nsec >= TS_NSEC_PER_SEC) { |
| res->tv_sec++; |
| res->tv_nsec -= TS_NSEC_PER_SEC; |
| } |
| } |
| |
| static int ts_diff_to_polltimeout(const struct timespec *a, |
| const struct timespec *b) |
| { |
| struct timespec diff; |
| |
| memset(&diff, 0, sizeof(diff)); |
| |
| diff.tv_sec = a->tv_sec - b->tv_sec; |
| diff.tv_nsec = a->tv_nsec - b->tv_nsec; |
| if (a->tv_nsec < b->tv_nsec) { |
| diff.tv_nsec += TS_NSEC_PER_SEC; |
| diff.tv_sec--; |
| } |
| |
| if ((diff.tv_sec - 1) > (INT_MAX / 1000)) |
| return INT_MAX; |
| return diff.tv_sec * 1000 + diff.tv_nsec / (TS_NSEC_PER_SEC / 1000); |
| } |
| |
| static void ts_delay_from_millis(uint32_t millis, struct timespec *res) |
| { |
| res->tv_sec = millis / 1000; |
| res->tv_nsec = (millis % 1000) * (TS_NSEC_PER_SEC / 1000); |
| } |
| |
| static TEEC_Result poll_with_timeout(struct pollfd *pfd, nfds_t nfds, |
| uint32_t timeout) |
| { |
| int to = 0; |
| int r = 0; |
| struct timespec now; |
| struct timespec until; |
| |
| memset(&now, 0, sizeof(now)); |
| memset(&until, 0, sizeof(until)); |
| |
| if (timeout == OPTEE_MRC_SOCKET_TIMEOUT_BLOCKING) { |
| to = -1; |
| } else { |
| struct timespec delay; |
| |
| memset(&delay, 0, sizeof(delay)); |
| |
| ts_delay_from_millis(timeout, &delay); |
| |
| if (clock_gettime(CLOCK_REALTIME, &now)) |
| return TEEC_ERROR_GENERIC; |
| |
| ts_add(&now, &delay, &until); |
| } |
| |
| while (true) { |
| if (to != -1) |
| to = ts_diff_to_polltimeout(&until, &now); |
| |
| r = poll(pfd, nfds, to); |
| if (!r) |
| return TEE_ISOCKET_ERROR_TIMEOUT; |
| if (r == -1) { |
| /* |
| * If we're interrupted by a signal treat |
| * recalculate the timeout (if needed) and wait |
| * again. |
| */ |
| if (errno == EINTR) { |
| if (to != -1 && |
| clock_gettime(CLOCK_REALTIME, &now)) |
| return TEEC_ERROR_GENERIC; |
| continue; |
| } |
| return TEEC_ERROR_BAD_PARAMETERS; |
| } |
| return TEEC_SUCCESS; |
| } |
| } |
| |
| static TEEC_Result write_with_timeout(int fd, const void *buf, size_t *blen, |
| uint32_t timeout) |
| { |
| TEEC_Result res = TEEC_ERROR_GENERIC; |
| struct pollfd pfd = { .fd = fd, .events = POLLOUT }; |
| ssize_t r = 0; |
| |
| res = poll_with_timeout(&pfd, 1, timeout); |
| if (res != TEEC_SUCCESS) |
| return res; |
| |
| r = write(fd, buf, *blen); |
| if (r == -1) { |
| if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) |
| return TEE_ISOCKET_ERROR_TIMEOUT; |
| return TEEC_ERROR_BAD_PARAMETERS; |
| } |
| *blen = r; |
| return TEEC_SUCCESS; |
| } |
| |
| static TEEC_Result tee_socket_send(size_t num_params, |
| struct tee_ioctl_param *params) |
| { |
| TEEC_Result res = TEEC_ERROR_GENERIC; |
| int handle = 0; |
| int fd = 0; |
| uint32_t instance_id = 0; |
| void *buf = NULL; |
| size_t bytes = 0; |
| |
| if (num_params != 3 || |
| !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) || |
| !chk_pt(params + 1, TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT) || |
| !chk_pt(params + 2, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| instance_id = params[0].b; |
| handle = params[0].c; |
| fd = sock_handle_to_fd(instance_id, handle); |
| if (fd < 0) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| buf = tee_supp_param_to_va(params + 1); |
| bytes = MEMREF_SIZE(params + 1); |
| res = write_with_timeout(fd, buf, &bytes, params[2].a); |
| if (res == TEEC_SUCCESS) |
| params[2].b = bytes; |
| return res; |
| } |
| |
| static ssize_t recv_with_out_flags(int fd, void *buf, size_t len, int inflags, |
| int *out_flags) |
| { |
| ssize_t r = 0; |
| |
| while (true) { |
| struct iovec iov = { .iov_base = buf, .iov_len = len, }; |
| struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1, }; |
| |
| r = recvmsg(fd, &msg, inflags); |
| if (r < 0) { |
| /* |
| * If the syscall was just interrupted by a signal |
| * delivery, try again. |
| */ |
| if (errno == EINTR) |
| continue; |
| return r; |
| } |
| |
| *out_flags = msg.msg_flags; |
| return r; |
| } |
| } |
| |
| static TEEC_Result read_with_timeout(int fd, void *buf, size_t *blen, |
| uint32_t timeout) |
| { |
| TEEC_Result res = TEEC_ERROR_GENERIC; |
| struct pollfd pfd = { .fd = fd, .events = POLLIN }; |
| int socktype = 0; |
| socklen_t l = sizeof(socktype); |
| size_t peek_len = 0; |
| int out_flags = 0; |
| ssize_t r = 0; |
| int e = 0; |
| |
| if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &socktype, &l)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| if (*blen) { |
| /* If *blen == 0, the timeout parameter has no effect. */ |
| res = poll_with_timeout(&pfd, 1, timeout); |
| if (res != TEEC_SUCCESS) |
| return res; |
| } |
| |
| if ((socktype == SOCK_DGRAM && *blen < SUPP_MAX_PEEK_LEN) || !*blen) { |
| /* Check how much data we have queued. */ |
| void *b = malloc(SUPP_MAX_PEEK_LEN); |
| |
| if (!b) |
| return TEEC_ERROR_OUT_OF_MEMORY; |
| r = recv_with_out_flags(fd, b, SUPP_MAX_PEEK_LEN, |
| MSG_PEEK | MSG_DONTWAIT, &out_flags); |
| e = errno; |
| free(b); |
| if (r < 0) |
| goto err; |
| |
| /* |
| * If the message was truncated we know that it's at least |
| * one byte larger. |
| */ |
| if (out_flags & MSG_TRUNC) |
| r++; |
| |
| if (!*blen) { |
| *blen = r; |
| return TEEC_SUCCESS; |
| } |
| |
| peek_len = r; |
| } |
| |
| r = recv_with_out_flags(fd, buf, *blen, MSG_DONTWAIT, &out_flags); |
| if (r == -1) { |
| e = errno; |
| goto err; |
| } |
| if (socktype == SOCK_DGRAM && (out_flags & MSG_TRUNC)) { |
| /* |
| * The datagram has been truncated, return the best length |
| * we have to indicate that. |
| */ |
| if (peek_len > (size_t)r) |
| *blen = peek_len; |
| else |
| *blen = r + 1; |
| } else { |
| *blen = r; |
| } |
| return TEEC_SUCCESS; |
| |
| err: |
| if (e == EAGAIN || e == EWOULDBLOCK) { |
| /* |
| * If *blen is supplied as 0 then we're not supposed wait |
| * for data so the call to poll has been skipped. In case |
| * there is no data available recvmsg() will return an |
| * error with errno set to EAGAIN or EWOULDBLOCK. |
| */ |
| if (!*blen) |
| return TEEC_SUCCESS; |
| return TEE_ISOCKET_ERROR_TIMEOUT; |
| } |
| return TEEC_ERROR_BAD_PARAMETERS; |
| } |
| |
| static TEEC_Result tee_socket_recv(size_t num_params, |
| struct tee_ioctl_param *params) |
| { |
| TEEC_Result res = TEEC_ERROR_GENERIC; |
| int handle = 0; |
| int fd = 0; |
| uint32_t instance_id = 0; |
| void *buf = NULL; |
| size_t bytes = 0; |
| |
| if (num_params != 3 || |
| !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) || |
| !chk_pt(params + 1, TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT) || |
| !chk_pt(params + 2, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| instance_id = params[0].b; |
| handle = params[0].c; |
| fd = sock_handle_to_fd(instance_id, handle); |
| if (fd < 0) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| buf = tee_supp_param_to_va(params + 1); |
| |
| bytes = MEMREF_SIZE(params + 1); |
| res = read_with_timeout(fd, buf, &bytes, params[2].a); |
| if (res == TEEC_SUCCESS) |
| MEMREF_SIZE(params + 1) = bytes; |
| |
| return res; |
| } |
| |
| static TEEC_Result tee_socket_ioctl_tcp(int fd, uint32_t command, |
| void *buf, size_t *blen) |
| { |
| switch (command) { |
| case TEE_TCP_SET_RECVBUF: |
| if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, buf, *blen)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| return TEEC_SUCCESS; |
| case TEE_TCP_SET_SENDBUF: |
| if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, buf, *blen)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| return TEEC_SUCCESS; |
| default: |
| return TEEC_ERROR_NOT_SUPPORTED; |
| } |
| } |
| |
| static TEEC_Result sa_set_port(struct sockaddr *sa, socklen_t slen, |
| uint16_t port) |
| { |
| if (sa->sa_family == AF_INET) { |
| struct sockaddr_in *sain = (void *)sa; |
| |
| if (slen < (socklen_t)sizeof(*sain)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| sain->sin_port = htons(port); |
| |
| return TEEC_SUCCESS; |
| } |
| |
| if (sa->sa_family == AF_INET6) { |
| struct sockaddr_in6 *sain6 = (void *)sa; |
| |
| if (slen < (socklen_t)sizeof(*sain6)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| sain6->sin6_port = htons(port); |
| |
| return TEEC_SUCCESS; |
| } |
| |
| return TEEC_ERROR_BAD_PARAMETERS; |
| } |
| |
| static TEEC_Result sa_get_port(struct sockaddr *sa, socklen_t slen, |
| uint16_t *port) |
| { |
| if (sa->sa_family == AF_INET) { |
| struct sockaddr_in *sain = (void *)sa; |
| |
| if (slen < (socklen_t)sizeof(*sain)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| *port = ntohs(sain->sin_port); |
| |
| return TEEC_SUCCESS; |
| } |
| |
| if (sa->sa_family == AF_INET6) { |
| struct sockaddr_in6 *sain6 = (void *)sa; |
| |
| if (slen < (socklen_t)sizeof(*sain6)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| *port = ntohs(sain6->sin6_port); |
| |
| return TEEC_SUCCESS; |
| } |
| |
| return TEEC_ERROR_BAD_PARAMETERS; |
| } |
| |
| static TEEC_Result udp_changeaddr(int fd, int family, const char *server, |
| uint16_t port) |
| { |
| TEEC_Result r = TEE_ISOCKET_ERROR_HOSTNAME; |
| struct addrinfo *res0 = NULL; |
| struct addrinfo *res = NULL; |
| char port_name[10] = { 0 }; |
| struct addrinfo hints; |
| |
| memset(&hints, 0, sizeof(hints)); |
| |
| snprintf(port_name, sizeof(port_name), "%" PRIu16, port); |
| |
| hints.ai_family = family; |
| hints.ai_socktype = SOCK_DGRAM; |
| if (getaddrinfo(server, port_name, &hints, &res0)) |
| return TEE_ISOCKET_ERROR_HOSTNAME; |
| for (res = res0; res; res = res->ai_next) { |
| if (connect(fd, res->ai_addr, res->ai_addrlen)) { |
| if (errno == ETIMEDOUT) |
| r = TEE_ISOCKET_ERROR_TIMEOUT; |
| else |
| r = TEEC_ERROR_COMMUNICATION; |
| continue; |
| } |
| r = TEEC_SUCCESS; |
| break; |
| } |
| freeaddrinfo(res0); |
| |
| return r; |
| } |
| |
| static TEEC_Result tee_socket_ioctl_udp(int fd, uint32_t command, |
| void *buf, size_t *blen) |
| { |
| TEEC_Result res = TEEC_ERROR_GENERIC; |
| uint16_t port = 0; |
| struct sockaddr_storage sass; |
| struct sockaddr *sa = (struct sockaddr *)&sass; |
| socklen_t len = sizeof(sass); |
| |
| memset(&sass, 0, sizeof(sass)); |
| |
| if (getpeername(fd, sa, &len)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| switch (command) { |
| case TEE_UDP_CHANGEADDR: |
| res = sa_get_port(sa, len, &port); |
| if (res != TEEC_SUCCESS) |
| return res; |
| |
| if (!blen || *((char *)buf + *blen - 1) != '\0') |
| return TEE_ISOCKET_ERROR_HOSTNAME; |
| |
| return udp_changeaddr(fd, sa->sa_family, buf, port); |
| case TEE_UDP_CHANGEPORT: |
| if (*blen != sizeof(port)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| memcpy(&port, buf, sizeof(port)); |
| res = sa_set_port(sa, len, port); |
| if (res != TEEC_SUCCESS) |
| return res; |
| if (connect(fd, sa, len)) |
| return TEEC_ERROR_GENERIC; |
| return TEEC_SUCCESS; |
| default: |
| return TEEC_ERROR_NOT_SUPPORTED; |
| } |
| } |
| |
| static TEEC_Result tee_socket_ioctl(size_t num_params, |
| struct tee_ioctl_param *params) |
| { |
| TEEC_Result res = TEEC_ERROR_GENERIC; |
| int handle = 0; |
| int fd = 0; |
| uint32_t instance_id = 0; |
| uint32_t command = 0; |
| void *buf = NULL; |
| int socktype = 0; |
| socklen_t l = 0; |
| size_t sz = 0; |
| |
| if (num_params != 3 || |
| !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) || |
| !chk_pt(params + 1, TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT) || |
| !chk_pt(params + 2, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| instance_id = params[0].b; |
| handle = params[0].c; |
| command = params[2].a; |
| fd = sock_handle_to_fd(instance_id, handle); |
| if (fd < 0) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| l = sizeof(socktype); |
| if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &socktype, &l)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| buf = tee_supp_param_to_va(params + 1); |
| |
| switch (socktype) { |
| case SOCK_STREAM: |
| sz = MEMREF_SIZE(params + 1); |
| res = tee_socket_ioctl_tcp(fd, command, buf, &sz); |
| MEMREF_SIZE(params + 1) = sz; |
| return res; |
| case SOCK_DGRAM: |
| sz = MEMREF_SIZE(params + 1); |
| res = tee_socket_ioctl_udp(fd, command, buf, &sz); |
| MEMREF_SIZE(params + 1) = sz; |
| return res; |
| default: |
| return TEEC_ERROR_BAD_PARAMETERS; |
| } |
| } |
| |
| TEEC_Result tee_socket_process(size_t num_params, |
| struct tee_ioctl_param *params) |
| { |
| if (!num_params || !tee_supp_param_is_value(params)) |
| return TEEC_ERROR_BAD_PARAMETERS; |
| |
| switch (params->a) { |
| case OPTEE_MRC_SOCKET_OPEN: |
| return tee_socket_open(num_params, params); |
| case OPTEE_MRC_SOCKET_CLOSE: |
| return tee_socket_close(num_params, params); |
| case OPTEE_MRC_SOCKET_CLOSE_ALL: |
| return tee_socket_close_all(num_params, params); |
| case OPTEE_MRC_SOCKET_SEND: |
| return tee_socket_send(num_params, params); |
| case OPTEE_MRC_SOCKET_RECV: |
| return tee_socket_recv(num_params, params); |
| case OPTEE_MRC_SOCKET_IOCTL: |
| return tee_socket_ioctl(num_params, params); |
| default: |
| return TEEC_ERROR_BAD_PARAMETERS; |
| } |
| } |