// Copyright 2012 Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #if defined(HAVE_CONFIG_H) # include "config.h" #endif #include "run.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include "env.h" #include "error.h" #include "fs.h" #include "text.h" /// Path to the temporary work directory to use. const char* kyua_run_tmpdir = KYUA_TMPDIR; #undef KYUA_TMPDIR // We really want to use the variable, not the macro. /// Whether we got a signal or not. static volatile bool signal_fired = false; /// Whether the process timed out or not. static volatile bool process_timed_out = false; /// If not -1, PID of the process to forcibly kill when we get a signal. /// /// Must be protected by protect() and unprotect(). static volatile pid_t pid_to_kill = -1; /// Whether we are holding signals or not. static bool protected = false; /// Magic exit code to denote an error while preparing the subprocess. static const int exit_setup_child = 124; /// Magic exit code to denote an error in exec(3) that we do not handle. static const int exit_exec_unknown = 123; /// Magic exit code to denote an EACCES error in exec(3). static const int exit_exec_eacces = 122; /// Magic exit code to denote an ENOENT error in exec(3). static const int exit_exec_enoent = 121; /// Area to save the original SIGHUP handler. static struct sigaction old_sighup; /// Area to save the original SIGINT handler. static struct sigaction old_sigint; /// Area to save the original SIGTERM handler. static struct sigaction old_sigterm; /// Area to save the original SIGALRM handler. static struct sigaction old_sigalrm; /// Area to save the original realtime timer. static struct itimerval old_timer; /// Masks or unmasks all the signals programmed by this module. /// /// \param operation One of SIG_BLOCK or SIG_UNBLOCK. static void mask_handlers(const int operation) { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGALRM); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGHUP); sigaddset(&mask, SIGTERM); const int ret = sigprocmask(operation, &mask, NULL); assert(ret != -1); } /// Masks all signals programmed by this module. static void protect(void) { mask_handlers(SIG_BLOCK); protected = true; } /// Unmasks all signals programmed by this module. static void unprotect(void) { protected = false; mask_handlers(SIG_UNBLOCK); } /// Handler for signals that should abort execution. /// /// When called the first time, this handler kills any running subprocess so /// that the cleanup routines can proceed. Calling this a second time aborts /// execution of the program. /// /// \param unused_signo Number of the captured signal. static void cleanup_handler(const int KYUA_DEFS_UNUSED_PARAM(signo)) { static const char* clean_message = "Signal caught; cleaning up...\n"; static const char* abort_message = "Double signal caught; aborting...\n"; protect(); if (!signal_fired) { signal_fired = true; if (write(STDERR_FILENO, clean_message, strlen(clean_message)) == -1) { // Ignore. } if (pid_to_kill != -1) { kill(pid_to_kill, SIGKILL); pid_to_kill = -1; } unprotect(); } else { if (write(STDERR_FILENO, abort_message, strlen(abort_message)) == -1) { // Ignore. } if (pid_to_kill != -1) { kill(pid_to_kill, SIGKILL); pid_to_kill = -1; } abort(); } } /// Handler for signals that should terminate the active subprocess. /// /// \param unused_signo Number of the captured signal. static void timeout_handler(const int KYUA_DEFS_UNUSED_PARAM(signo)) { static const char* message = "Subprocess timed out; sending KILL " "signal...\n"; protect(); process_timed_out = true; if (write(STDERR_FILENO, message, strlen(message)) == -1) { // Ignore. } if (pid_to_kill != -1) { kill(pid_to_kill, SIGKILL); pid_to_kill = -1; } unprotect(); } /// Installs a signal handler. /// /// \param signo Number of the signal to program. /// \param handler Handler for the signal. /// \param [out] old_sa Pointer to the sigaction structure in which to save the /// current signal handler data. static void setup_signal(const int signo, void (*handler)(const int), struct sigaction* old_sa) { struct sigaction sa; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; const int ret = sigaction(signo, &sa, old_sa); assert(ret != -1); } /// Installs a timer. /// /// \param seconds Deadline for the timer. /// \param [out] old_itimerval Pointer to the itimerval structure in which to /// save the current timer data into. static void setup_timer(const int seconds, struct itimerval* old_itimerval) { struct itimerval new_timer; new_timer.it_interval.tv_sec = 0; new_timer.it_interval.tv_usec = 0; new_timer.it_value.tv_sec = seconds; new_timer.it_value.tv_usec = 0; const int ret = setitimer(ITIMER_REAL, &new_timer, old_itimerval); assert(ret != -1); } /// Resets the environment of the process to a known state. /// /// \param work_directory Path to the work directory being used. /// /// \return An error if there is a problem configuring the environment /// variables, or OK if successful. Note that if this returns an error, we have /// left the environment in an inconsistent state. static kyua_error_t prepare_environment(const char* work_directory) { kyua_error_t error; // TODO(jmmv): It might be better to do the opposite: just pass a good known // set of variables to the child (aka HOME, PATH, ...). But how do we // determine this minimum set? const char* to_unset[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", NULL }; const char** iter; for (iter = to_unset; *iter != NULL; ++iter) { error = kyua_env_unset(*iter); if (kyua_error_is_set(error)) return error; } error = kyua_env_set("HOME", work_directory); if (kyua_error_is_set(error)) return error; error = kyua_env_set("TZ", "UTC"); if (kyua_error_is_set(error)) return error; error = kyua_env_set("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value"); if (kyua_error_is_set(error)) return error; assert(!kyua_error_is_set(error)); return error; } /// Resets all signals to their default handlers. static void reset_signals(void) { int signo; for (signo = 1; signo <= LAST_SIGNO; ++signo) { if (signo == SIGKILL || signo == SIGSTOP) { // Don't attempt to reset immutable signals. continue; } struct sigaction sa; sa.sa_handler = SIG_DFL; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; (void)sigaction(signo, &sa, NULL); } } /// Raises core size limit to its possible maximum. /// /// This is a best-effort operation. There is no guarantee that the operation /// will yield a large-enough limit to generate any possible core file. static void unlimit_core_size(void) { struct rlimit rl; { const int ret = getrlimit(RLIMIT_CORE, &rl); assert(ret != -1); } rl.rlim_cur = rl.rlim_max; const int ret = setrlimit(RLIMIT_CORE, &rl) != -1; assert(ret != -1); } /// Cleans up the container process to run a new child. /// /// If there is any error during the setup, the new process is terminated /// with an error code. /// /// \param run_params End-user parameters that describe how to isolate the /// newly-created process. static void setup_child(const kyua_run_params_t* run_params) { setpgid(getpid(), getpid()); if (chdir(run_params->work_directory) == -1) err(exit_setup_child, "chdir(%s) failed", run_params->work_directory); unlimit_core_size(); reset_signals(); const kyua_error_t error = prepare_environment(run_params->work_directory); if (kyua_error_is_set(error)) kyua_error_err(exit_setup_child, error, "Failed to configure the " "environment"); (void)umask(0022); if (run_params->unprivileged_group != getgid()) { if (setgid(run_params->unprivileged_group) == -1) err(exit_setup_child, "setgid(%ld) failed; uid is %ld and gid " "is %ld", (long int)run_params->unprivileged_group, (long int)getuid(), (long int)getgid()); } if (run_params->unprivileged_user != getuid()) { if (setuid(run_params->unprivileged_user) == -1) err(exit_setup_child, "setuid(%ld) failed; uid is %ld and gid " "is %ld", (long int)run_params->unprivileged_user, (long int)getuid(), (long int)getgid()); } } /// Constructs a path to a work directory based on a template. /// /// \param template Template of the work directory to create. Should be a /// basename and must include the XXXXXX placeholder string. The directory /// is created within the temporary directory specified by the TMPDIR /// environment variable or the builtin value in kyua_run_tmpdir. /// \param [out] work_directory Pointer to a dynamically-allocated string /// containing the full path to the work directory. The caller is /// responsible for releasing this string. /// /// \return An error if there is a problem (most likely OOM), or OK otherwise. static kyua_error_t build_work_directory_path(const char* template, char** work_directory) { assert(strstr(template, "XXXXXX") != NULL); const char* tmpdir = getenv("TMPDIR"); if (tmpdir == NULL) return kyua_text_printf(work_directory, "%s/%s", kyua_run_tmpdir, template); else return kyua_text_printf(work_directory, "%s/%s", tmpdir, template); } /// Initializes the run_params parameters with default values. /// /// \param [out] run_params The object to initialize. void kyua_run_params_init(kyua_run_params_t* run_params) { run_params->timeout_seconds = 60; run_params->unprivileged_user = getuid(); run_params->unprivileged_group = getgid(); run_params->work_directory = "."; } /// Executes a program and exits if there is a problem. /// /// This routine is supposed to be used in conjunction with kyua_run_fork and /// kyua_run_wait so that the various return codes of the exec system call are /// properly propagated to the parent process. /// /// \param program Path to the program to run. /// \param args Arguments to the program. void kyua_run_exec(const char* program, const char* const* args) { (void)execvp(program, KYUA_DEFS_UNCONST(args)); switch (errno) { case EACCES: exit(exit_exec_eacces); case ENOENT: exit(exit_exec_enoent); default: err(exit_exec_unknown, "execvp failed"); } } /// Forks and isolates the child process. /// /// The created subprocess must be waited for with kyua_run_wait(). /// /// \param run_params Parameters that describe how to isolate the newly-created /// process. /// \param [out] out_pid The PID of the child in the parent, or 0 in the child. /// The value left here should only be accessed if this function does not /// return an error. /// /// \return An error object if fork(2) fails. kyua_error_t kyua_run_fork(const kyua_run_params_t* run_params, pid_t* const out_pid) { protect(); pid_t pid = fork(); if (pid == -1) { unprotect(); *out_pid = pid; // Not necessary, but avoid mistakes in the caller. return kyua_libc_error_new(errno, "fork failed"); } else if (pid == 0) { unprotect(); setup_child(run_params); *out_pid = pid; return kyua_error_ok(); } else { pid_to_kill = pid; unprotect(); setup_signal(SIGALRM, timeout_handler, &old_sigalrm); process_timed_out = false; setup_timer(run_params->timeout_seconds, &old_timer); *out_pid = pid; return kyua_error_ok(); } } /// Waits for a process started via kyua_run_fork. /// /// \param pid The PID of the child to wait for. /// \param [out] status The exit status of the awaited process. /// \param [out] timed_out Whether the process timed out or not. /// /// \return An error if the process failed due to an problem in kyua_run_exec. /// However, note that the wait for the process itself is expected to have been /// successful. kyua_error_t kyua_run_wait(const pid_t pid, int* status, bool* timed_out) { int tmp_status; const pid_t waited_pid = waitpid(pid, &tmp_status, 0); assert(pid == waited_pid); protect(); (void)setitimer(ITIMER_REAL, &old_timer, NULL); (void)sigaction(SIGALRM, &old_sigalrm, NULL); pid_to_kill = -1; unprotect(); killpg(pid, SIGKILL); if (WIFEXITED(tmp_status)) { if (WEXITSTATUS(tmp_status) == exit_setup_child) { return kyua_generic_error_new("Failed to isolate subprocess; " "see stderr for details"); } else if (WEXITSTATUS(tmp_status) == exit_exec_eacces) { return kyua_libc_error_new(EACCES, "execvp failed"); } else if (WEXITSTATUS(tmp_status) == exit_exec_enoent) { return kyua_libc_error_new(ENOENT, "execvp failed"); } else if (WEXITSTATUS(tmp_status) == exit_exec_unknown) { return kyua_generic_error_new("execvp failed; see stderr for " "details"); } else { // Fall-through. } } *status = tmp_status; *timed_out = process_timed_out; return kyua_error_ok(); } /// Creates a temporary directory for use by a subprocess. /// /// The temporary directory must be deleted with kyua_run_work_directory_leave. /// /// \param template Template of the work directory to create. Should be a /// basename and must include the XXXXXX placeholder string. The directory /// is created within the temporary directory specified by the TMPDIR /// environment variable or the builtin value. /// \param uid User to set the owner of the directory to. /// \param gid Group to set the owner of the directory to. /// \param [out] out_work_directory Updated with a pointer to a dynamic string /// holding the path to the created work directory. This must be passed as /// is to kyua_run_work_directory_leave, which takes care of freeing the /// memory. /// /// \return An error code if there is a problem creating the directory. kyua_error_t kyua_run_work_directory_enter(const char* template, const uid_t uid, const gid_t gid, char** out_work_directory) { kyua_error_t error = kyua_error_ok(); signal_fired = false; setup_signal(SIGHUP, cleanup_handler, &old_sighup); setup_signal(SIGINT, cleanup_handler, &old_sigint); setup_signal(SIGTERM, cleanup_handler, &old_sigterm); char* work_directory; error = build_work_directory_path(template, &work_directory); if (kyua_error_is_set(error)) goto err_signals; if (mkdtemp(work_directory) == NULL) { error = kyua_libc_error_new(errno, "mkdtemp(%s) failed", work_directory); goto err_work_directory_variable; } if (uid != getuid() || gid != getgid()) { if (chown(work_directory, uid, gid) == -1) { error = kyua_libc_error_new(errno, "chown(%s, %ld, %ld) failed; uid is %ld and gid is %ld", work_directory, (long int)uid, (long int)gid, (long int)getuid(), (long int)getgid()); goto err_work_directory_file; } } *out_work_directory = work_directory; assert(!kyua_error_is_set(error)); goto out; err_work_directory_file: (void)rmdir(work_directory); err_work_directory_variable: free(work_directory); err_signals: (void)sigaction(SIGTERM, &old_sigterm, NULL); (void)sigaction(SIGINT, &old_sigint, NULL); (void)sigaction(SIGHUP, &old_sighup, NULL); out: return error; } /// Deletes a temporary directory created by kyua_run_work_directory_enter(). /// /// \param [in,out] work_directory The pointer to the work_directory string as /// originally returned by kyua_run_work_directory_leave(). This is /// explicitly invalidated by this function to clearly denote that this /// performs the memory releasing. /// /// \return An error code if the cleanup of the directory fails. Note that any /// intermediate errors during the cleanup are sent to stderr. kyua_error_t kyua_run_work_directory_leave(char** work_directory) { kyua_error_t error = kyua_fs_cleanup(*work_directory); free(*work_directory); *work_directory = NULL; (void)sigaction(SIGTERM, &old_sigterm, NULL); (void)sigaction(SIGHUP, &old_sighup, NULL); (void)sigaction(SIGINT, &old_sigint, NULL); // At this point, we have cleaned up the work directory and we could // (should?) re-deliver the signal to ourselves so that we terminated with // the right code. However, we just let this return and allow the caller // code to perform any other cleanups instead. return error; }