exec.c
1 /* Copyright (c) 2018 Pablo Marcos Oltra <pablo.marcos.oltra@gmail.com> 2 * 3 * Permission is hereby granted, free of charge, to any person obtaining a copy 4 * of this software and associated documentation files (the "Software"), to deal 5 * in the Software without restriction, including without limitation the rights 6 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 * copies of the Software, and to permit persons to whom the Software is 8 * furnished to do so, subject to the following conditions: 9 * 10 * The above copyright notice and this permission notice shall be included in all 11 * copies or substantial portions of the Software. 12 * 13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 * SOFTWARE. 20 */ 21 22 /* 23 * This exec.so library is intended to restore the environment of the AppImage's 24 * parent process. This is done to avoid library clashing of bundled libraries 25 * with external processes. e.g when running the web browser 26 * 27 * The intended usage is as follows: 28 * 29 * 1. This library is injected to the dynamic loader through LD_PRELOAD 30 * automatically in AppRun **only** if `usr/optional/exec.so` exists: 31 * e.g `LD_PRELOAD=$APPDIR/usr/optional/exec.so My.AppImage` 32 * 33 * 2. This library will intercept calls to new processes and will detect whether 34 * those calls are for binaries within the AppImage bundle or external ones. 35 * 36 * 3. In case it's an internal process, it will not change anything. 37 * In case it's an external process, it will restore the environment of 38 * the AppImage parent by reading `/proc/[pid]/environ`. 39 * This is the conservative approach taken. 40 */ 41 42 #define _GNU_SOURCE 43 44 #include <stdlib.h> 45 #include <unistd.h> 46 #include <dlfcn.h> 47 #include <string.h> 48 #include <errno.h> 49 50 #include "env.h" 51 52 typedef int (*execve_func_t)(const char *filename, char *const argv[], char *const envp[]); 53 54 #define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) 55 56 static const char* get_fullpath(const char *filename) { 57 // Try to get the canonical path in case it's a relative path or symbolic 58 // link. Otherwise, use which to get the fullpath of the binary 59 char *fullpath = canonicalize_file_name(filename); 60 DEBUG("filename %s, canonical path %s\n", filename, fullpath); 61 if (fullpath) 62 return fullpath; 63 64 return filename; 65 } 66 67 static int is_external_process(const char *filename) { 68 const char *appdir = getenv("APPDIR"); 69 if (!appdir) 70 return 0; 71 DEBUG("APPDIR = %s\n", appdir); 72 73 return strncmp(filename, appdir, MIN(strlen(filename), strlen(appdir))); 74 } 75 76 static int exec_common(execve_func_t function, const char *filename, char* const argv[], char* const envp[]) { 77 const char *fullpath = get_fullpath(filename); 78 DEBUG("filename %s, fullpath %s\n", filename, fullpath); 79 char* const *env = envp; 80 if (is_external_process(fullpath)) { 81 DEBUG("External process detected. Restoring env vars from parent %d\n", get_parent_pid()); 82 env = read_parent_env(); 83 if (!env) { 84 env = envp; 85 DEBUG("Error restoring env vars from parent\n"); 86 } 87 } 88 int ret = function(filename, argv, env); 89 90 if (fullpath != filename) 91 free((char*)fullpath); 92 if (env != envp) 93 env_free(env); 94 95 return ret; 96 } 97 98 int execve(const char *filename, char *const argv[], char *const envp[]) { 99 DEBUG("execve call hijacked: %s\n", filename); 100 execve_func_t execve_orig = dlsym(RTLD_NEXT, "execve"); 101 if (!execve_orig) 102 DEBUG("Error getting execve original symbol: %s\n", strerror(errno)); 103 104 return exec_common(execve_orig, filename, argv, envp); 105 } 106 107 int execv(const char *filename, char *const argv[]) { 108 DEBUG("execv call hijacked: %s\n", filename); 109 return execve(filename, argv, environ); 110 } 111 112 int execvpe(const char *filename, char *const argv[], char *const envp[]) { 113 DEBUG("execvpe call hijacked: %s\n", filename); 114 execve_func_t execve_orig = dlsym(RTLD_NEXT, "execvpe"); 115 if (!execve_orig) 116 DEBUG("Error getting execvpe original symbol: %s\n", strerror(errno)); 117 118 return exec_common(execve_orig, filename, argv, envp); 119 } 120 121 int execvp(const char *filename, char *const argv[]) { 122 DEBUG("execvp call hijacked: %s\n", filename); 123 return execvpe(filename, argv, environ); 124 } 125 126 #ifdef EXEC_TEST 127 int main(int argc, char *argv[]) { 128 putenv("APPIMAGE_CHECKRT_DEBUG=1"); 129 DEBUG("EXEC TEST\n"); 130 execv("./env_test", argv); 131 132 return 0; 133 } 134 #endif