/* -*- coding: utf-8 -*- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ /** * This file implements a shared library. This library can be pre-loaded by * the dynamic linker of the Operating System (OS). It implements a few function * related to process creation. By pre-load this library the executed process * uses these functions instead of those from the standard library. * * The idea here is to inject a logic before call the real methods. The logic is * to dump the call into a file. To call the real method this library is doing * the job of the dynamic linker. * * The only input for the log writing is about the destination directory. * This is passed as environment variable. */ #include "config.h" #include #include #include #include #include #include #include #include #if defined HAVE_POSIX_SPAWN || defined HAVE_POSIX_SPAWNP #include #endif #if defined HAVE_NSGETENVIRON # include #else extern char **environ; #endif #define ENV_OUTPUT "INTERCEPT_BUILD_TARGET_DIR" #ifdef APPLE # define ENV_FLAT "DYLD_FORCE_FLAT_NAMESPACE" # define ENV_PRELOAD "DYLD_INSERT_LIBRARIES" # define ENV_SIZE 3 #else # define ENV_PRELOAD "LD_PRELOAD" # define ENV_SIZE 2 #endif #define DLSYM(TYPE_, VAR_, SYMBOL_) \ union { \ void *from; \ TYPE_ to; \ } cast; \ if (0 == (cast.from = dlsym(RTLD_NEXT, SYMBOL_))) { \ perror("bear: dlsym"); \ exit(EXIT_FAILURE); \ } \ TYPE_ const VAR_ = cast.to; typedef char const * bear_env_t[ENV_SIZE]; static int bear_capture_env_t(bear_env_t *env); static int bear_reset_env_t(bear_env_t *env); static void bear_release_env_t(bear_env_t *env); static char const **bear_update_environment(char *const envp[], bear_env_t *env); static char const **bear_update_environ(char const **in, char const *key, char const *value); static char **bear_get_environment(); static void bear_report_call(char const *fun, char const *const argv[]); static char const **bear_strings_build(char const *arg, va_list *ap); static char const **bear_strings_copy(char const **const in); static char const **bear_strings_append(char const **in, char const *e); static size_t bear_strings_length(char const *const *in); static void bear_strings_release(char const **); static bear_env_t env_names = { ENV_OUTPUT , ENV_PRELOAD #ifdef ENV_FLAT , ENV_FLAT #endif }; static bear_env_t initial_env = { 0 , 0 #ifdef ENV_FLAT , 0 #endif }; static int initialized = 0; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static void on_load(void) __attribute__((constructor)); static void on_unload(void) __attribute__((destructor)); #ifdef HAVE_EXECVE static int call_execve(const char *path, char *const argv[], char *const envp[]); #endif #ifdef HAVE_EXECVP static int call_execvp(const char *file, char *const argv[]); #endif #ifdef HAVE_EXECVPE static int call_execvpe(const char *file, char *const argv[], char *const envp[]); #endif #ifdef HAVE_EXECVP2 static int call_execvP(const char *file, const char *search_path, char *const argv[]); #endif #ifdef HAVE_EXECT static int call_exect(const char *path, char *const argv[], char *const envp[]); #endif #ifdef HAVE_POSIX_SPAWN static int call_posix_spawn(pid_t *restrict pid, const char *restrict path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv[restrict], char *const envp[restrict]); #endif #ifdef HAVE_POSIX_SPAWNP static int call_posix_spawnp(pid_t *restrict pid, const char *restrict file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv[restrict], char *const envp[restrict]); #endif /* Initialization method to Captures the relevant environment variables. */ static void on_load(void) { pthread_mutex_lock(&mutex); if (!initialized) initialized = bear_capture_env_t(&initial_env); pthread_mutex_unlock(&mutex); } static void on_unload(void) { pthread_mutex_lock(&mutex); bear_release_env_t(&initial_env); initialized = 0; pthread_mutex_unlock(&mutex); } /* These are the methods we are try to hijack. */ #ifdef HAVE_EXECVE int execve(const char *path, char *const argv[], char *const envp[]) { bear_report_call(__func__, (char const *const *)argv); return call_execve(path, argv, envp); } #endif #ifdef HAVE_EXECV #ifndef HAVE_EXECVE #error can not implement execv without execve #endif int execv(const char *path, char *const argv[]) { bear_report_call(__func__, (char const *const *)argv); char * const * envp = bear_get_environment(); return call_execve(path, argv, envp); } #endif #ifdef HAVE_EXECVPE int execvpe(const char *file, char *const argv[], char *const envp[]) { bear_report_call(__func__, (char const *const *)argv); return call_execvpe(file, argv, envp); } #endif #ifdef HAVE_EXECVP int execvp(const char *file, char *const argv[]) { bear_report_call(__func__, (char const *const *)argv); return call_execvp(file, argv); } #endif #ifdef HAVE_EXECVP2 int execvP(const char *file, const char *search_path, char *const argv[]) { bear_report_call(__func__, (char const *const *)argv); return call_execvP(file, search_path, argv); } #endif #ifdef HAVE_EXECT int exect(const char *path, char *const argv[], char *const envp[]) { bear_report_call(__func__, (char const *const *)argv); return call_exect(path, argv, envp); } #endif #ifdef HAVE_EXECL # ifndef HAVE_EXECVE # error can not implement execl without execve # endif int execl(const char *path, const char *arg, ...) { va_list args; va_start(args, arg); char const **argv = bear_strings_build(arg, &args); va_end(args); bear_report_call(__func__, (char const *const *)argv); char * const * envp = bear_get_environment(); int const result = call_execve(path, (char *const *)argv, envp); bear_strings_release(argv); return result; } #endif #ifdef HAVE_EXECLP # ifndef HAVE_EXECVP # error can not implement execlp without execvp # endif int execlp(const char *file, const char *arg, ...) { va_list args; va_start(args, arg); char const **argv = bear_strings_build(arg, &args); va_end(args); bear_report_call(__func__, (char const *const *)argv); int const result = call_execvp(file, (char *const *)argv); bear_strings_release(argv); return result; } #endif #ifdef HAVE_EXECLE # ifndef HAVE_EXECVE # error can not implement execle without execve # endif // int execle(const char *path, const char *arg, ..., char * const envp[]); int execle(const char *path, const char *arg, ...) { va_list args; va_start(args, arg); char const **argv = bear_strings_build(arg, &args); char const **envp = va_arg(args, char const **); va_end(args); bear_report_call(__func__, (char const *const *)argv); int const result = call_execve(path, (char *const *)argv, (char *const *)envp); bear_strings_release(argv); return result; } #endif #ifdef HAVE_POSIX_SPAWN int posix_spawn(pid_t *restrict pid, const char *restrict path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv[restrict], char *const envp[restrict]) { bear_report_call(__func__, (char const *const *)argv); return call_posix_spawn(pid, path, file_actions, attrp, argv, envp); } #endif #ifdef HAVE_POSIX_SPAWNP int posix_spawnp(pid_t *restrict pid, const char *restrict file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv[restrict], char *const envp[restrict]) { bear_report_call(__func__, (char const *const *)argv); return call_posix_spawnp(pid, file, file_actions, attrp, argv, envp); } #endif /* These are the methods which forward the call to the standard implementation. */ #ifdef HAVE_EXECVE static int call_execve(const char *path, char *const argv[], char *const envp[]) { typedef int (*func)(const char *, char *const *, char *const *); DLSYM(func, fp, "execve"); char const **const menvp = bear_update_environment(envp, &initial_env); int const result = (*fp)(path, argv, (char *const *)menvp); bear_strings_release(menvp); return result; } #endif #ifdef HAVE_EXECVPE static int call_execvpe(const char *file, char *const argv[], char *const envp[]) { typedef int (*func)(const char *, char *const *, char *const *); DLSYM(func, fp, "execvpe"); char const **const menvp = bear_update_environment(envp, &initial_env); int const result = (*fp)(file, argv, (char *const *)menvp); bear_strings_release(menvp); return result; } #endif #ifdef HAVE_EXECVP static int call_execvp(const char *file, char *const argv[]) { typedef int (*func)(const char *file, char *const argv[]); DLSYM(func, fp, "execvp"); bear_env_t current_env; bear_capture_env_t(¤t_env); bear_reset_env_t(&initial_env); int const result = (*fp)(file, argv); bear_reset_env_t(¤t_env); bear_release_env_t(¤t_env); return result; } #endif #ifdef HAVE_EXECVP2 static int call_execvP(const char *file, const char *search_path, char *const argv[]) { typedef int (*func)(const char *, const char *, char *const *); DLSYM(func, fp, "execvP"); bear_env_t current_env; bear_capture_env_t(¤t_env); bear_reset_env_t(&initial_env); int const result = (*fp)(file, search_path, argv); bear_reset_env_t(¤t_env); bear_release_env_t(¤t_env); return result; } #endif #ifdef HAVE_EXECT static int call_exect(const char *path, char *const argv[], char *const envp[]) { typedef int (*func)(const char *, char *const *, char *const *); DLSYM(func, fp, "exect"); char const **const menvp = bear_update_environment(envp, &initial_env); int const result = (*fp)(path, argv, (char *const *)menvp); bear_strings_release(menvp); return result; } #endif #ifdef HAVE_POSIX_SPAWN static int call_posix_spawn(pid_t *restrict pid, const char *restrict path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv[restrict], char *const envp[restrict]) { typedef int (*func)(pid_t *restrict, const char *restrict, const posix_spawn_file_actions_t *, const posix_spawnattr_t *restrict, char *const *restrict, char *const *restrict); DLSYM(func, fp, "posix_spawn"); char const **const menvp = bear_update_environment(envp, &initial_env); int const result = (*fp)(pid, path, file_actions, attrp, argv, (char *const *restrict)menvp); bear_strings_release(menvp); return result; } #endif #ifdef HAVE_POSIX_SPAWNP static int call_posix_spawnp(pid_t *restrict pid, const char *restrict file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv[restrict], char *const envp[restrict]) { typedef int (*func)(pid_t *restrict, const char *restrict, const posix_spawn_file_actions_t *, const posix_spawnattr_t *restrict, char *const *restrict, char *const *restrict); DLSYM(func, fp, "posix_spawnp"); char const **const menvp = bear_update_environment(envp, &initial_env); int const result = (*fp)(pid, file, file_actions, attrp, argv, (char *const *restrict)menvp); bear_strings_release(menvp); return result; } #endif /* this method is to write log about the process creation. */ static void bear_report_call(char const *fun, char const *const argv[]) { static int const GS = 0x1d; static int const RS = 0x1e; static int const US = 0x1f; if (!initialized) return; pthread_mutex_lock(&mutex); const char *cwd = getcwd(NULL, 0); if (0 == cwd) { perror("bear: getcwd"); exit(EXIT_FAILURE); } char const * const out_dir = initial_env[0]; size_t const path_max_length = strlen(out_dir) + 32; char filename[path_max_length]; if (-1 == snprintf(filename, path_max_length, "%s/%d.cmd", out_dir, getpid())) { perror("bear: snprintf"); exit(EXIT_FAILURE); } FILE * fd = fopen(filename, "a+"); if (0 == fd) { perror("bear: fopen"); exit(EXIT_FAILURE); } fprintf(fd, "%d%c", getpid(), RS); fprintf(fd, "%d%c", getppid(), RS); fprintf(fd, "%s%c", fun, RS); fprintf(fd, "%s%c", cwd, RS); size_t const argc = bear_strings_length(argv); for (size_t it = 0; it < argc; ++it) { fprintf(fd, "%s%c", argv[it], US); } fprintf(fd, "%c", GS); if (fclose(fd)) { perror("bear: fclose"); exit(EXIT_FAILURE); } free((void *)cwd); pthread_mutex_unlock(&mutex); } /* update environment assure that chilren processes will copy the desired * behaviour */ static int bear_capture_env_t(bear_env_t *env) { int status = 1; for (size_t it = 0; it < ENV_SIZE; ++it) { char const * const env_value = getenv(env_names[it]); char const * const env_copy = (env_value) ? strdup(env_value) : env_value; (*env)[it] = env_copy; status &= (env_copy) ? 1 : 0; } return status; } static int bear_reset_env_t(bear_env_t *env) { int status = 1; for (size_t it = 0; it < ENV_SIZE; ++it) { if ((*env)[it]) { setenv(env_names[it], (*env)[it], 1); } else { unsetenv(env_names[it]); } } return status; } static void bear_release_env_t(bear_env_t *env) { for (size_t it = 0; it < ENV_SIZE; ++it) { free((void *)(*env)[it]); (*env)[it] = 0; } } static char const **bear_update_environment(char *const envp[], bear_env_t *env) { char const **result = bear_strings_copy((char const **)envp); for (size_t it = 0; it < ENV_SIZE && (*env)[it]; ++it) result = bear_update_environ(result, env_names[it], (*env)[it]); return result; } static char const **bear_update_environ(char const *envs[], char const *key, char const * const value) { // find the key if it's there size_t const key_length = strlen(key); char const **it = envs; for (; (it) && (*it); ++it) { if ((0 == strncmp(*it, key, key_length)) && (strlen(*it) > key_length) && ('=' == (*it)[key_length])) break; } // allocate a environment entry size_t const value_length = strlen(value); size_t const env_length = key_length + value_length + 2; char *env = malloc(env_length); if (0 == env) { perror("bear: malloc [in env_update]"); exit(EXIT_FAILURE); } if (-1 == snprintf(env, env_length, "%s=%s", key, value)) { perror("bear: snprintf"); exit(EXIT_FAILURE); } // replace or append the environment entry if (it && *it) { free((void *)*it); *it = env; return envs; } return bear_strings_append(envs, env); } static char **bear_get_environment() { #if defined HAVE_NSGETENVIRON return *_NSGetEnviron(); #else return environ; #endif } /* util methods to deal with string arrays. environment and process arguments * are both represented as string arrays. */ static char const **bear_strings_build(char const *const arg, va_list *args) { char const **result = 0; size_t size = 0; for (char const *it = arg; it; it = va_arg(*args, char const *)) { result = realloc(result, (size + 1) * sizeof(char const *)); if (0 == result) { perror("bear: realloc"); exit(EXIT_FAILURE); } char const *copy = strdup(it); if (0 == copy) { perror("bear: strdup"); exit(EXIT_FAILURE); } result[size++] = copy; } result = realloc(result, (size + 1) * sizeof(char const *)); if (0 == result) { perror("bear: realloc"); exit(EXIT_FAILURE); } result[size++] = 0; return result; } static char const **bear_strings_copy(char const **const in) { size_t const size = bear_strings_length(in); char const **const result = malloc((size + 1) * sizeof(char const *)); if (0 == result) { perror("bear: malloc"); exit(EXIT_FAILURE); } char const **out_it = result; for (char const *const *in_it = in; (in_it) && (*in_it); ++in_it, ++out_it) { *out_it = strdup(*in_it); if (0 == *out_it) { perror("bear: strdup"); exit(EXIT_FAILURE); } } *out_it = 0; return result; } static char const **bear_strings_append(char const **const in, char const *const e) { size_t size = bear_strings_length(in); char const **result = realloc(in, (size + 2) * sizeof(char const *)); if (0 == result) { perror("bear: realloc"); exit(EXIT_FAILURE); } result[size++] = e; result[size++] = 0; return result; } static size_t bear_strings_length(char const *const *const in) { size_t result = 0; for (char const *const *it = in; (it) && (*it); ++it) ++result; return result; } static void bear_strings_release(char const **in) { for (char const *const *it = in; (it) && (*it); ++it) { free((void *)*it); } free((void *)in); }