| /* |
| * Copyright 2012, The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <portability.h> |
| #include <unistd.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <errno_portable.h> |
| #include <filefd_portable.h> |
| #include <signal_portable.h> |
| #include <sys/atomics.h> |
| |
| #define PORTABLE_TAG "filefd_portable" |
| #include <log_portable.h> |
| |
| /* |
| * Maintaining a list of special file descriptors in lib-portable: |
| * --------------------------------------------------------------- |
| * |
| * These are file descriptors which were opened with system calls |
| * which make it possible to read kernel data structures via the |
| * read system call. See man pages for: |
| * signalfd(2) |
| * eventfd(2) |
| * timerfd_create(2) |
| * |
| * The files conditioned with signalfd(2) need to have their reads |
| * intercepted to correct signal numbers. This is done using this table |
| * of mapped files. |
| * |
| * The signalfd(2) semantics are maintained across execve(2) by exporting |
| * and importing environment variables for file descriptors that are not |
| * marked as close-on-execute. For example testing import code with: |
| * Eg: |
| * export ANDROID_PORTABLE_MAPPED_FILE_DESCRIPTORS=10,17 |
| * export ANDROID_PORTABLE_MAPPED_FILE_TYPES=2,1 |
| * |
| * Where |
| * filefd_mapped_file[10] = SIGNAL_FD_TYPE:2 |
| * filefd_FD_CLOEXEC_file[10] = 0; |
| * and |
| * filefd_mapped_file[17] = EVENT_FD_TYPE:1 |
| * filefd_FD_CLOEXEC_file[17] = 0; |
| * |
| * A table of CLOEXEC_files is maintained via call-backs |
| * in open_portable() and fcntl_portable() which indicates |
| * the files with close-on-execute semantics. |
| * |
| * The signalfd(2) fork(2) and thread semantics are not |
| * affected by the mapping of signalfd() file descriptor reads. |
| * |
| * This algorithm requires that threads have the same sharing |
| * attributes for file descriptors and memory and will be disabled |
| * by a call from clone() if the environment is unsuitable for it's use. |
| */ |
| |
| static char *fd_env_name = "ANDROID_PORTABLE_MAPPED_FILE_DESCRIPTORS"; |
| static char *type_env_name = "ANDROID_PORTABLE_MAPPED_FILE_TYPES"; |
| static enum filefd_type filefd_mapped_file[__FD_SETSIZE]; |
| static int filefd_FD_CLOEXEC_file[__FD_SETSIZE]; |
| |
| static volatile int filefd_mapped_files = 0; |
| static volatile int filefd_enabled = 1; |
| |
| /* |
| * Assuming sizeof(int)==4, and __FD_SETSIZE < 10000 each token will |
| * occupy a maximum of 5 characters (4 digits + delimiter:','). The tokens |
| * are the numbers above, a file descriptor (0..9999), and the filefd_type's |
| * which are a single digit. |
| * |
| * The arrays used to manipulate the environment variables are allocated using |
| * malloc to avoid overrunning the stack. |
| */ |
| #if __FD_SETSIZE >= 10000 |
| #error MAX_ENV_SIZE must be increased |
| #endif |
| |
| #define MAX_ENV_SIZE (__FD_SETSIZE * 5) |
| |
| static int export_fd_env() |
| { |
| const int max_env_size = MAX_ENV_SIZE; |
| int type_env_bytes_remaining = max_env_size; |
| char *type_env_allocated = NULL, *type_env; |
| int fd_env_bytes_remaining = max_env_size; |
| char *fd_env_allocated = NULL, *fd_env; |
| int exported_file_descriptors = 0; |
| enum filefd_type fd_type; |
| int overwrite = 1; |
| int fd_count = 0; |
| int saved_errno; |
| int fd_cloexec; |
| int len; |
| int rv1; |
| int rv2; |
| int rv; |
| int fd; |
| |
| ALOGV("%s:() {", __func__); |
| |
| saved_errno = *REAL(__errno)(); |
| |
| type_env_allocated = malloc(max_env_size); |
| fd_env_allocated = malloc(max_env_size); |
| if (type_env_allocated == NULL || fd_env_allocated == NULL) { |
| ALOGE("%s: type_env_allocated:%p, fd_env_allocated:%p; FIXME!", __func__, |
| type_env_allocated, fd_env_allocated); |
| |
| rv = -1; |
| goto done; |
| } else { |
| ALOGV("%s: type_env_allocated:%p, fd_env_allocated:%p;", __func__, |
| type_env_allocated, fd_env_allocated); |
| } |
| |
| type_env = type_env_allocated; |
| fd_env = fd_env_allocated; |
| |
| for (fd = 0; fd < __FD_SETSIZE; fd++) { |
| fd_type = filefd_mapped_file[fd]; |
| if (fd_type != UNUSED_FD_TYPE) { |
| ++fd_count; |
| ALOGV("%s: fd_type = %d = filefd_mapped_file[fd:%d]; ++fdcount:%d;", __func__, |
| fd_type, fd, fd_count); |
| |
| fd_cloexec = filefd_FD_CLOEXEC_file[fd]; |
| ALOGV("%s: fd_cloexec = %d = filefd_FD_CLOEXEC_file[fd:%d];", __func__, |
| fd_cloexec, fd); |
| |
| if (fd_cloexec == 0) { |
| rv = snprintf(fd_env, fd_env_bytes_remaining, "%d,", fd); |
| ASSERT(rv > 0); |
| fd_env += rv; |
| fd_env_bytes_remaining -= rv; |
| rv = snprintf(type_env, type_env_bytes_remaining, "%d,", filefd_mapped_file[fd]); |
| ASSERT(rv > 0); |
| type_env += rv; |
| type_env_bytes_remaining -= rv; |
| exported_file_descriptors++; |
| } |
| |
| /* |
| * There is a chance of inconsistent results here if |
| * another thread is updating the array while it was |
| * being copied, but this code is only run during exec |
| * so the state of the file descriptors that the child |
| * sees will be inconsistent anyway. |
| */ |
| if (fd_count == filefd_mapped_files) |
| break; |
| } |
| } |
| if (fd_count != filefd_mapped_files) { |
| ALOGE("%s: fd_count:%d != filefd_mapped_files:%d; [Likely Race; add futex?]", __func__, |
| fd_count, filefd_mapped_files); |
| |
| } |
| if (exported_file_descriptors == 0) { |
| rv1 = unsetenv(fd_env_name); |
| rv2 = unsetenv(type_env_name); |
| if (rv1 != 0 || rv2 != 0) { |
| ALOGV("%s: Note: unsetenv() failed!", __func__); |
| } |
| rv = 0; |
| } else { |
| if (fd_env > fd_env_allocated) { |
| fd_env--; /* backup fd_env to last ',' */ |
| } |
| *fd_env = '\0'; |
| |
| if (type_env > type_env_allocated) { |
| type_env--; /* backup type_env to last ',' */ |
| } |
| *type_env = '\0'; |
| |
| rv = setenv(fd_env_name, fd_env_allocated, overwrite); |
| if (rv != 0) { |
| ALOGE("%s: rv:%d = setenv(fd_env_name:'%s', fd_env_allocated:'%s' ...);", __func__, |
| rv, fd_env_name, fd_env_allocated); |
| } else { |
| ALOGV("%s: rv:%d = setenv(fd_env_name:'%s', fd_env_allocated:'%s' ...);", __func__, |
| rv, fd_env_name, fd_env_allocated); |
| } |
| if (rv != 0) goto done; |
| |
| rv = setenv(type_env_name, type_env_allocated, overwrite); |
| |
| if (rv != 0) { |
| ALOGE("%s: rv:%d = setenv(type_env_name:'%s', type_env_allocated:'%s' ...);", |
| __func__, rv, type_env_name, type_env_allocated); |
| } else { |
| ALOGV("%s: rv:%d = setenv(type_env_name:'%s', type_env_allocated:'%s' ...);", |
| __func__, rv, type_env_name, type_env_allocated); |
| } |
| } |
| |
| done: |
| if (type_env_allocated) |
| free(type_env_allocated); |
| |
| if (fd_env_allocated) |
| free(fd_env_allocated); |
| |
| *REAL(__errno)() = saved_errno; |
| |
| ALOGV("%s: return(rv:%d); }", __func__, rv); |
| return rv; |
| } |
| |
| |
| static int import_fd_env(int verify) |
| { |
| char *type_env_allocated = NULL; |
| char *fd_env_allocated = NULL; |
| char *type_token_saved_ptr; |
| char *fd_token_saved_ptr; |
| enum filefd_type fd_type; |
| char *type_env, *fd_env; |
| int saved_errno; |
| char *type_token; |
| char *fd_token; |
| int rv = 0; |
| int fd; |
| |
| ALOGV("%s:(verify:%d) {", __func__, verify); |
| |
| saved_errno = *REAL(__errno)(); |
| |
| /* |
| * get file descriptor environment pointer and make a |
| * a copy of the string. |
| */ |
| fd_env = getenv(fd_env_name); |
| if (fd_env == NULL) { |
| ALOGV("%s: fd_env = NULL = getenv('%s');", __func__, |
| fd_env_name); |
| goto done; |
| } else { |
| ALOGV("%s: fd_env = '%s' = getenv('%s');", __func__, |
| fd_env, fd_env_name); |
| |
| fd_env_allocated = malloc(strlen(fd_env)+1); |
| if (fd_env_allocated == NULL) { |
| ALOGE("%s: fd_env_allocated = NULL; malloc failed", __func__); |
| goto done; |
| } |
| strcpy(fd_env_allocated, fd_env); |
| } |
| |
| /* |
| * get file descriptor environment pointer and make a copy of |
| * the string to our stack. |
| */ |
| type_env = getenv(type_env_name); |
| if (type_env == NULL) { |
| ALOGV("%s: type_env = NULL = getenv(type_env_name:'%s');", __func__, |
| type_env_name); |
| goto done; |
| } else { |
| ALOGV("%s: type_env = '%s' = getenv(type_env_name:'%s');", __func__, |
| type_env, type_env_name); |
| |
| type_env_allocated = malloc(strlen(type_env)+1); |
| if (type_env_allocated == NULL) { |
| ALOGE("%s: type_env_allocated = NULL; malloc failed", __func__); |
| goto done; |
| } |
| strcpy(type_env_allocated, type_env); |
| } |
| |
| /* |
| * Setup strtok_r(), use it to parse the env tokens, and |
| * initialise the filefd_mapped_file array. |
| */ |
| fd_token = strtok_r(fd_env_allocated, ",", &fd_token_saved_ptr); |
| type_token = strtok_r(type_env_allocated, ",", &type_token_saved_ptr); |
| while (fd_token && type_token) { |
| fd = atoi(fd_token); |
| ASSERT(fd >= 0 ); |
| ASSERT(fd < __FD_SETSIZE); |
| |
| fd_type = (enum filefd_type) atoi(type_token); |
| ASSERT(fd_type > UNUSED_FD_TYPE); |
| ASSERT(fd_type < MAX_FD_TYPE); |
| |
| if (fd >= 0 && fd < __FD_SETSIZE) { |
| if (fd_type > UNUSED_FD_TYPE && fd_type < MAX_FD_TYPE) { |
| if (verify) { |
| ASSERT(filefd_mapped_file[fd] == fd_type); |
| ALOGV("%s: filefd_mapped_file[fd:%d] == fd_type:%d;", __func__, |
| fd, fd_type); |
| } else { |
| ASSERT(filefd_mapped_file[fd] == UNUSED_FD_TYPE); |
| |
| __atomic_inc(&filefd_mapped_files); |
| ALOGV("%s: ++filefd_mapped_files:%d;", __func__, |
| filefd_mapped_files); |
| |
| filefd_mapped_file[fd] = fd_type; |
| ALOGV("%s: filefd_mapped_file[fd:%d] = fd_type:%d;", __func__, |
| fd, fd_type); |
| } |
| } |
| } |
| |
| fd_token = strtok_r(NULL, ",", &fd_token_saved_ptr); |
| type_token = strtok_r(NULL, ",", &type_token_saved_ptr); |
| } |
| |
| done: |
| if (type_env_allocated) |
| free(type_env_allocated); |
| if (fd_env_allocated) |
| free(fd_env_allocated); |
| |
| *REAL(__errno)() = saved_errno; |
| |
| ALOGV("%s: return(rv:%d); }", __func__, rv); |
| return rv; |
| } |
| |
| |
| /* |
| * This function will get run by the linker when the library is loaded. |
| */ |
| static void __attribute__ ((constructor)) linker_import_fd_env(void) |
| { |
| int rv; |
| int verify_consistancy = 0; |
| |
| ALOGV(" "); |
| ALOGV("%s() {", __func__); |
| |
| rv = import_fd_env(verify_consistancy); /* File type table not verified. */ |
| |
| ALOGV("%s: }", __func__); |
| } |
| |
| |
| __hidden void filefd_opened(int fd, enum filefd_type fd_type) |
| { |
| ALOGV("%s(fd:%d) {", __func__, fd); |
| |
| if (fd >= 0 && fd < __FD_SETSIZE) { |
| if (filefd_mapped_file[fd] == UNUSED_FD_TYPE) { |
| __atomic_inc(&filefd_mapped_files); |
| filefd_mapped_file[fd] = fd_type; |
| } |
| ASSERT(filefd_mapped_file[fd] == fd_type); |
| } |
| |
| ALOGV("%s: }", __func__); |
| } |
| |
| __hidden void filefd_closed(int fd) |
| { |
| ALOGV("%s(fd:%d) {", __func__, fd); |
| |
| if (fd >= 0 && fd < __FD_SETSIZE) { |
| if (filefd_mapped_file[fd] != UNUSED_FD_TYPE) { |
| filefd_mapped_file[fd] = UNUSED_FD_TYPE; |
| filefd_FD_CLOEXEC_file[fd] = 0; |
| __atomic_dec(&filefd_mapped_files); |
| } |
| } |
| ALOGV("%s: }", __func__); |
| } |
| |
| |
| __hidden void filefd_CLOEXEC_enabled(int fd) |
| { |
| ALOGV("%s:(fd:%d) {", __func__, fd); |
| |
| if (fd >= 0 && fd < __FD_SETSIZE) { |
| filefd_FD_CLOEXEC_file[fd] = 1; |
| } |
| |
| ALOGV("%s: }", __func__); |
| } |
| |
| __hidden void filefd_CLOEXEC_disabled(int fd) |
| { |
| ALOGV("%s:(fd:%d) {", __func__, fd); |
| |
| if (fd >= 0 && fd < __FD_SETSIZE) { |
| filefd_FD_CLOEXEC_file[fd] = 0; |
| } |
| |
| ALOGV("%s: }", __func__); |
| } |
| |
| |
| __hidden void filefd_disable_mapping() |
| { |
| ALOGV("%s:() {", __func__); |
| |
| filefd_enabled = 0; |
| |
| ALOGV("%s: }", __func__); |
| } |
| |
| |
| int WRAP(close)(int fd) |
| { |
| int rv; |
| |
| ALOGV(" "); |
| ALOGV("%s(fd:%d) {", __func__, fd); |
| |
| rv = REAL(close)(fd); |
| filefd_closed(fd); |
| |
| ALOGV("%s: return(rv:%d); }", __func__, rv); |
| return rv; |
| } |
| |
| |
| int WRAP(read)(int fd, void *buf, size_t count) |
| { |
| int rv; |
| enum filefd_type fd_type; |
| |
| ALOGV(" "); |
| ALOGV("%s(fd:%d, buf:0x%p, count:%d) {", __func__, |
| fd, buf, count); |
| |
| fd_type = filefd_mapped_file[fd]; |
| ALOGV("%s:fd_type:%d", __func__, |
| fd_type); |
| |
| switch (fd_type) { |
| /* Reads on these descriptors are portable; no need to be mapped. */ |
| case UNUSED_FD_TYPE: |
| case EVENT_FD_TYPE: |
| case INOTIFY_FD_TYPE: |
| case TIMER_FD_TYPE: |
| rv = REAL(read)(fd, buf, count); |
| break; |
| |
| /* The read() of a signalfd() file descriptor needs to be mapped. */ |
| case SIGNAL_FD_TYPE: |
| if (filefd_enabled) { |
| rv = read_signalfd_mapper(fd, buf, count); |
| } else { |
| rv = REAL(read)(fd, buf, count); |
| } |
| break; |
| |
| default: |
| ALOGE("Unknown fd_type:%d!", fd_type); |
| rv = REAL(read)(fd, buf, count); |
| break; |
| } |
| |
| ALOGV("%s: return(rv:%d); }", __func__, rv); |
| return rv; |
| } |
| |
| |
| /* |
| * Export PORTABLE environment variables before execve(). |
| * Tries a second time if it detects an extremely unlikely |
| * race condition. |
| */ |
| int WRAP(execve)(const char *filename, char *const argv[], char *const envp[]) |
| { |
| int rv; |
| int mapped_files = filefd_mapped_files; |
| int verify_consistancy = 1; |
| |
| ALOGV(" "); |
| ALOGV("%s(filename:%p, argv:%p, envp:%p) {", __func__, |
| filename, argv, envp); |
| |
| export_fd_env(); |
| |
| if (mapped_files != filefd_mapped_files) { |
| export_fd_env(); |
| } |
| import_fd_env(verify_consistancy); /* File type table consistancy verified. */ |
| |
| rv = REAL(execve)(filename, argv, envp); |
| |
| ALOGV("%s: return(rv:%d); }", __func__, rv); |
| return rv; |
| } |
| |