| /* Author: Trusted Computer Solutions, Inc. |
| * |
| * Modified: |
| * Yuichi Nakamura <[email protected]> |
| - Stubs are used when DISABLE_SETRANS is defined, |
| it is to reduce size for such as embedded devices. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <netdb.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <unistd.h> |
| #include <sys/uio.h> |
| #include "selinux_internal.h" |
| #include "setrans_internal.h" |
| |
| #ifndef DISABLE_SETRANS |
| static unsigned char has_setrans; |
| |
| // Simple cache |
| static __thread char * prev_t2r_trans = NULL; |
| static __thread char * prev_t2r_raw = NULL; |
| static __thread char * prev_r2t_trans = NULL; |
| static __thread char * prev_r2t_raw = NULL; |
| static __thread char *prev_r2c_trans = NULL; |
| static __thread char * prev_r2c_raw = NULL; |
| |
| static pthread_once_t once = PTHREAD_ONCE_INIT; |
| static pthread_key_t destructor_key; |
| static int destructor_key_initialized = 0; |
| static __thread char destructor_initialized; |
| |
| /* |
| * setransd_open |
| * |
| * This function opens a socket to the setransd. |
| * Returns: on success, a file descriptor ( >= 0 ) to the socket |
| * on error, a negative value |
| */ |
| static int setransd_open(void) |
| { |
| struct sockaddr_un addr; |
| int fd; |
| #ifdef SOCK_CLOEXEC |
| fd = socket(PF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); |
| if (fd < 0 && errno == EINVAL) |
| #endif |
| { |
| fd = socket(PF_UNIX, SOCK_STREAM, 0); |
| if (fd >= 0) |
| if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { |
| close(fd); |
| return -1; |
| } |
| } |
| if (fd < 0) |
| return -1; |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.sun_family = AF_UNIX; |
| |
| if (strlcpy(addr.sun_path, SETRANS_UNIX_SOCKET, sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) { |
| close(fd); |
| errno = EOVERFLOW; |
| return -1; |
| } |
| |
| if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| /* Returns: 0 on success, <0 on failure */ |
| static int |
| send_request(int fd, uint32_t function, const char *data1, const char *data2) |
| { |
| struct msghdr msgh; |
| struct iovec iov[5]; |
| uint32_t data1_size; |
| uint32_t data2_size; |
| ssize_t count, expected; |
| unsigned int i; |
| |
| if (fd < 0) |
| return -1; |
| |
| if (!data1) |
| data1 = ""; |
| if (!data2) |
| data2 = ""; |
| |
| data1_size = strlen(data1) + 1; |
| data2_size = strlen(data2) + 1; |
| |
| iov[0].iov_base = &function; |
| iov[0].iov_len = sizeof(function); |
| iov[1].iov_base = &data1_size; |
| iov[1].iov_len = sizeof(data1_size); |
| iov[2].iov_base = &data2_size; |
| iov[2].iov_len = sizeof(data2_size); |
| iov[3].iov_base = (char *)data1; |
| iov[3].iov_len = data1_size; |
| iov[4].iov_base = (char *)data2; |
| iov[4].iov_len = data2_size; |
| memset(&msgh, 0, sizeof(msgh)); |
| msgh.msg_iov = iov; |
| msgh.msg_iovlen = sizeof(iov) / sizeof(iov[0]); |
| |
| expected = 0; |
| for (i = 0; i < sizeof(iov) / sizeof(iov[0]); i++) |
| expected += iov[i].iov_len; |
| |
| while (((count = sendmsg(fd, &msgh, MSG_NOSIGNAL)) < 0) |
| && (errno == EINTR)) ; |
| if (count < 0 || count != expected) |
| return -1; |
| |
| return 0; |
| } |
| |
| /* Returns: 0 on success, <0 on failure */ |
| static int |
| receive_response(int fd, uint32_t function, char **outdata, int32_t * ret_val) |
| { |
| struct iovec resp_hdr[3]; |
| uint32_t func; |
| uint32_t data_size; |
| char *data; |
| struct iovec resp_data; |
| ssize_t count; |
| |
| if (fd < 0) |
| return -1; |
| |
| resp_hdr[0].iov_base = &func; |
| resp_hdr[0].iov_len = sizeof(func); |
| resp_hdr[1].iov_base = &data_size; |
| resp_hdr[1].iov_len = sizeof(data_size); |
| resp_hdr[2].iov_base = ret_val; |
| resp_hdr[2].iov_len = sizeof(*ret_val); |
| |
| while (((count = readv(fd, resp_hdr, 3)) < 0) && (errno == EINTR)) ; |
| if (count != (sizeof(func) + sizeof(data_size) + sizeof(*ret_val))) { |
| return -1; |
| } |
| |
| if (func != function || !data_size || data_size > MAX_DATA_BUF) { |
| return -1; |
| } |
| |
| data = malloc(data_size); |
| if (!data) |
| return -1; |
| /* coveriety doesn't realize that data will be initialized in readv */ |
| memset(data, 0, data_size); |
| |
| resp_data.iov_base = data; |
| resp_data.iov_len = data_size; |
| |
| while (((count = readv(fd, &resp_data, 1))) < 0 && (errno == EINTR)) ; |
| if (count < 0 || (uint32_t) count != data_size || |
| data[data_size - 1] != '\0') { |
| free(data); |
| return -1; |
| } |
| *outdata = data; |
| return 0; |
| } |
| |
| static int raw_to_trans_context(const char *raw, char **transp) |
| { |
| int ret; |
| int32_t ret_val; |
| int fd; |
| |
| *transp = NULL; |
| |
| fd = setransd_open(); |
| if (fd < 0) |
| return fd; |
| |
| ret = send_request(fd, RAW_TO_TRANS_CONTEXT, raw, NULL); |
| if (ret) |
| goto out; |
| |
| ret = receive_response(fd, RAW_TO_TRANS_CONTEXT, transp, &ret_val); |
| if (ret) |
| goto out; |
| |
| ret = ret_val; |
| out: |
| close(fd); |
| return ret; |
| } |
| |
| static int trans_to_raw_context(const char *trans, char **rawp) |
| { |
| int ret; |
| int32_t ret_val; |
| int fd; |
| |
| *rawp = NULL; |
| |
| fd = setransd_open(); |
| if (fd < 0) |
| return fd; |
| ret = send_request(fd, TRANS_TO_RAW_CONTEXT, trans, NULL); |
| if (ret) |
| goto out; |
| |
| ret = receive_response(fd, TRANS_TO_RAW_CONTEXT, rawp, &ret_val); |
| if (ret) |
| goto out; |
| |
| ret = ret_val; |
| out: |
| close(fd); |
| return ret; |
| } |
| |
| static int raw_context_to_color(const char *raw, char **colors) |
| { |
| int ret; |
| int32_t ret_val; |
| int fd; |
| |
| fd = setransd_open(); |
| if (fd < 0) |
| return fd; |
| |
| ret = send_request(fd, RAW_CONTEXT_TO_COLOR, raw, NULL); |
| if (ret) |
| goto out; |
| |
| ret = receive_response(fd, RAW_CONTEXT_TO_COLOR, colors, &ret_val); |
| if (ret) |
| goto out; |
| |
| ret = ret_val; |
| out: |
| close(fd); |
| return ret; |
| } |
| |
| static void setrans_thread_destructor(void __attribute__((unused)) *unused) |
| { |
| free(prev_t2r_trans); |
| free(prev_t2r_raw); |
| free(prev_r2t_trans); |
| free(prev_r2t_raw); |
| free(prev_r2c_trans); |
| free(prev_r2c_raw); |
| } |
| |
| void __attribute__((destructor)) setrans_lib_destructor(void); |
| |
| void __attribute__((destructor)) setrans_lib_destructor(void) |
| { |
| if (!has_setrans) |
| return; |
| if (destructor_key_initialized) |
| __selinux_key_delete(destructor_key); |
| } |
| |
| static inline void init_thread_destructor(void) |
| { |
| if (!has_setrans) |
| return; |
| if (destructor_initialized == 0) { |
| __selinux_setspecific(destructor_key, /* some valid address to please GCC */ &selinux_page_size); |
| destructor_initialized = 1; |
| } |
| } |
| |
| static void init_context_translations(void) |
| { |
| has_setrans = (access(SETRANS_UNIX_SOCKET, F_OK) == 0); |
| if (!has_setrans) |
| return; |
| if (__selinux_key_create(&destructor_key, setrans_thread_destructor) == 0) |
| destructor_key_initialized = 1; |
| } |
| |
| int selinux_trans_to_raw_context(const char * trans, |
| char ** rawp) |
| { |
| if (!trans) { |
| *rawp = NULL; |
| return 0; |
| } |
| |
| __selinux_once(once, init_context_translations); |
| init_thread_destructor(); |
| |
| if (!has_setrans) { |
| *rawp = strdup(trans); |
| goto out; |
| } |
| |
| if (prev_t2r_trans && strcmp(prev_t2r_trans, trans) == 0) { |
| *rawp = strdup(prev_t2r_raw); |
| } else { |
| free(prev_t2r_trans); |
| prev_t2r_trans = NULL; |
| free(prev_t2r_raw); |
| prev_t2r_raw = NULL; |
| if (trans_to_raw_context(trans, rawp)) |
| *rawp = strdup(trans); |
| if (*rawp) { |
| prev_t2r_trans = strdup(trans); |
| if (!prev_t2r_trans) |
| goto out; |
| prev_t2r_raw = strdup(*rawp); |
| if (!prev_t2r_raw) { |
| free(prev_t2r_trans); |
| prev_t2r_trans = NULL; |
| } |
| } |
| } |
| out: |
| return *rawp ? 0 : -1; |
| } |
| |
| |
| int selinux_raw_to_trans_context(const char * raw, |
| char ** transp) |
| { |
| if (!raw) { |
| *transp = NULL; |
| return 0; |
| } |
| |
| __selinux_once(once, init_context_translations); |
| init_thread_destructor(); |
| |
| if (!has_setrans) { |
| *transp = strdup(raw); |
| goto out; |
| } |
| |
| if (prev_r2t_raw && strcmp(prev_r2t_raw, raw) == 0) { |
| *transp = strdup(prev_r2t_trans); |
| } else { |
| free(prev_r2t_raw); |
| prev_r2t_raw = NULL; |
| free(prev_r2t_trans); |
| prev_r2t_trans = NULL; |
| if (raw_to_trans_context(raw, transp)) |
| *transp = strdup(raw); |
| if (*transp) { |
| prev_r2t_raw = strdup(raw); |
| if (!prev_r2t_raw) |
| goto out; |
| prev_r2t_trans = strdup(*transp); |
| if (!prev_r2t_trans) { |
| free(prev_r2t_raw); |
| prev_r2t_raw = NULL; |
| } |
| } |
| } |
| out: |
| return *transp ? 0 : -1; |
| } |
| |
| |
| int selinux_raw_context_to_color(const char * raw, char **transp) |
| { |
| if (!raw) { |
| *transp = NULL; |
| return -1; |
| } |
| |
| __selinux_once(once, init_context_translations); |
| init_thread_destructor(); |
| |
| if (!has_setrans) { |
| *transp = strdup(raw); |
| goto out; |
| } |
| |
| if (prev_r2c_raw && strcmp(prev_r2c_raw, raw) == 0) { |
| *transp = strdup(prev_r2c_trans); |
| } else { |
| free(prev_r2c_raw); |
| prev_r2c_raw = NULL; |
| free(prev_r2c_trans); |
| prev_r2c_trans = NULL; |
| if (raw_context_to_color(raw, transp)) |
| return -1; |
| if (*transp) { |
| prev_r2c_raw = strdup(raw); |
| if (!prev_r2c_raw) |
| goto out; |
| prev_r2c_trans = strdup(*transp); |
| if (!prev_r2c_trans) { |
| free(prev_r2c_raw); |
| prev_r2c_raw = NULL; |
| } |
| } |
| } |
| out: |
| return *transp ? 0 : -1; |
| } |
| |
| #else /*DISABLE_SETRANS*/ |
| |
| int selinux_trans_to_raw_context(const char * trans, |
| char ** rawp) |
| { |
| if (!trans) { |
| *rawp = NULL; |
| return 0; |
| } |
| |
| *rawp = strdup(trans); |
| |
| return *rawp ? 0 : -1; |
| } |
| |
| |
| int selinux_raw_to_trans_context(const char * raw, |
| char ** transp) |
| { |
| if (!raw) { |
| *transp = NULL; |
| return 0; |
| } |
| *transp = strdup(raw); |
| |
| return *transp ? 0 : -1; |
| } |
| |
| #endif /*DISABLE_SETRANS*/ |