/ src / exec.c
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