// Functions that we may safely call after fork(). #include "config.h" // IWYU pragma: keep #include #include #include #include #include #include #include #include #ifdef HAVE_SPAWN_H #include #endif #include #include "common.h" #include "exec.h" #include "flog.h" #include "io.h" #include "iothread.h" #include "job_group.h" #include "postfork.h" #include "proc.h" #include "redirection.h" #include "signal.h" #include "wutil.h" // IWYU pragma: keep #ifndef JOIN_THREADS_BEFORE_FORK #define JOIN_THREADS_BEFORE_FORK 0 #endif /// The number of times to try to call fork() before giving up. #define FORK_LAPS 5 /// The number of nanoseconds to sleep between attempts to call fork(). #define FORK_SLEEP_TIME 1000000 extern bool is_thompson_shell_script(const char *path); static char *get_interpreter(const char *command, char *buffer, size_t buff_size); /// Report the error code \p err for a failed setpgid call. void report_setpgid_error(int err, bool is_parent, pid_t desired_pgid, const job_t *j, const process_t *p) { char pid_buff[128]; char job_id_buff[128]; char getpgid_buff[128]; char job_pgid_buff[128]; char argv0[64]; char command[64]; format_long_safe(pid_buff, p->pid); format_long_safe(job_id_buff, j->job_id()); format_long_safe(getpgid_buff, getpgid(p->pid)); format_long_safe(job_pgid_buff, desired_pgid); narrow_string_safe(argv0, p->argv0()); narrow_string_safe(command, j->command_wcstr()); FLOGF_SAFE(warning, "Could not send %s %s, '%s' in job %s, '%s' from group %s to group %s", is_parent ? "child" : "self", pid_buff, argv0, job_id_buff, command, getpgid_buff, job_pgid_buff); if (is_windows_subsystem_for_linux() && errno == EPERM) { FLOGF_SAFE(warning, "Please update to Windows 10 1809/17763 or higher to address known issues " "with process groups and zombie processes."); } errno = err; switch (errno) { case EACCES: { FLOGF_SAFE(error, "setpgid: Process %s has already exec'd", pid_buff); break; } case EINVAL: { FLOGF_SAFE(error, "setpgid: pgid %s unsupported", getpgid_buff); break; } case EPERM: { FLOGF_SAFE(error, "setpgid: Process %s is a session leader or pgid %s does not match", pid_buff, getpgid_buff); break; } case ESRCH: { FLOGF_SAFE(error, "setpgid: Process ID %s does not match", pid_buff); break; } default: { char errno_buff[64]; format_long_safe(errno_buff, errno); FLOGF_SAFE(error, "setpgid: Unknown error number %s", errno_buff); break; } } } int execute_setpgid(pid_t pid, pid_t pgroup, bool is_parent) { // Historically we have looped here to support WSL. unsigned eperm_count = 0; for (;;) { if (setpgid(pid, pgroup) == 0) { return 0; } int err = errno; if (err == EACCES && is_parent) { // We are the parent process and our child has called exec(). // This is an unavoidable benign race. return 0; } else if (err == EINTR) { // Paranoia. continue; } else if (err == EPERM && eperm_count++ < 100) { // The setpgid(2) man page says that EPERM is returned only if attempts are made // to move processes into groups across session boundaries (which can never be // the case in fish, anywhere) or to change the process group ID of a session // leader (again, can never be the case). I'm pretty sure this is a WSL bug, as // we see the same with tcsetpgrp(2) in other places and it disappears on retry. FLOGF_SAFE(proc_pgroup, "setpgid(2) returned EPERM. Retrying"); continue; } #if defined(__BSD__) || defined(__APPLE__) // POSIX.1 doesn't specify that zombie processes are required to be considered extant and/or // children of the parent for purposes of setpgid(2). In particular, FreeBSD (at least up to // 12.2) does not consider a child that has already forked, exec'd, and exited to "exist" // and returns ESRCH (process not found) instead of EACCES (child has called exec). // See https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=251227 else if (err == ESRCH && is_parent) { // Handle this just like we would EACCES above, as we're virtually certain that // setpgid(2) was called against a process that was at least at one point in time a // valid child. return 0; } #endif return err; } } int child_setup_process(pid_t claim_tty_from, const job_t &job, bool is_forked, const dup2_list_t &dup2s) { // Note we are called in a forked child. for (const auto &act : dup2s.get_actions()) { int err; if (act.target < 0) { err = close(act.src); } else if (act.target != act.src) { // Normal redirection. err = dup2(act.src, act.target); } else { // This is a weird case like /bin/cmd 6< file.txt // The opened file (which is CLO_EXEC) wants to be dup2'd to its own fd. // We need to unset the CLO_EXEC flag. err = set_cloexec(act.src, false); } if (err < 0) { if (is_forked) { FLOGF_SAFE(warning, "failed to set up file descriptors in child_setup_process"); exit_without_destructors(1); } return err; } } if (claim_tty_from >= 0 && tcgetpgrp(STDIN_FILENO) == claim_tty_from) { // Assign the terminal within the child to avoid the well-known race between tcsetgrp() in // the parent and the child executing. We are not interested in error handling here, except // we try to avoid this for non-terminals; in particular pipelines often make non-terminal // stdin. // Only do this if the tty currently belongs to fish's pgrp. Don't try to steal it away from // another process which may happen if we are run in the background with job control // enabled. Note if stdin is not a tty, then tcgetpgrp() will return -1 and we will not // enter this. // Ensure this doesn't send us to the background (see #5963) signal(SIGTTIN, SIG_IGN); signal(SIGTTOU, SIG_IGN); (void)tcsetpgrp(STDIN_FILENO, getpid()); } sigset_t sigmask; sigemptyset(&sigmask); if (blocked_signals_for_job(job, &sigmask)) { sigprocmask(SIG_SETMASK, &sigmask, nullptr); } // Set the handling for job control signals back to the default. // Do this after any tcsetpgrp call so that we swallow SIGTTIN. signal_reset_handlers(); return 0; } /// This function is a wrapper around fork. If the fork calls fails with EAGAIN, it is retried /// FORK_LAPS times, with a very slight delay between each lap. If fork fails even then, the process /// will exit with an error message. pid_t execute_fork() { if (JOIN_THREADS_BEFORE_FORK) { // Make sure we have no outstanding threads before we fork. This is a pretty sketchy thing // to do here, both because exec.cpp shouldn't have to know about iothreads, and because the // completion handlers may do unexpected things. FLOGF_SAFE(iothread, "waiting for threads to drain."); iothread_drain_all(); } pid_t pid; struct timespec pollint; int i; for (i = 0; i < FORK_LAPS; i++) { pid = fork(); if (pid >= 0) { return pid; } if (errno != EAGAIN) { break; } pollint.tv_sec = 0; pollint.tv_nsec = FORK_SLEEP_TIME; // Don't sleep on the final lap - sleeping might change the value of errno, which will break // the error reporting below. if (i != FORK_LAPS - 1) { nanosleep(&pollint, nullptr); } } // These are all the errno numbers for fork() I can find. // Also ENOSYS, but I doubt anyone is running // fish on a platform without an MMU. switch (errno) { case EAGAIN: { // We should have retried these already? FLOGF_SAFE(error, "fork: Out of resources. Check RLIMIT_NPROC and pid_max."); break; } case ENOMEM: { FLOGF_SAFE(error, "fork: Out of memory."); break; } default: { char errno_buff[64]; format_long_safe(errno_buff, errno); FLOGF_SAFE(error, "fork: Unknown error number %s", errno_buff); break; } } FATAL_EXIT(); return 0; } #if FISH_USE_POSIX_SPAWN // Given an error code, if it is the first error, record it. // \return whether we have any error. bool posix_spawner_t::check_fail(int err) { if (error_ == 0) error_ = err; return error_ != 0; } posix_spawner_t::~posix_spawner_t() { if (attr_) { posix_spawnattr_destroy(this->attr()); } if (actions_) { posix_spawn_file_actions_destroy(this->actions()); } } posix_spawner_t::posix_spawner_t(const job_t *j, const dup2_list_t &dup2s) { // Initialize our fields. This may fail. { posix_spawnattr_t attr; if (check_fail(posix_spawnattr_init(&attr))) return; this->attr_ = attr; } { posix_spawn_file_actions_t actions; if (check_fail(posix_spawn_file_actions_init(&actions))) return; this->actions_ = actions; } // desired_pgid tracks the pgroup for the process. If it is none, the pgroup is left unchanged. // If it is zero, create a new pgroup from the pid. If it is >0, join that pgroup. maybe_t desired_pgid = none(); if (auto pgid = j->group->get_pgid()) { desired_pgid = *pgid; } else if (j->processes.front()->leads_pgrp) { desired_pgid = 0; } // Set the handling for job control signals back to the default. bool reset_signal_handlers = true; // Remove all signal blocks. bool reset_sigmask = true; // Set our flags. short flags = 0; if (reset_signal_handlers) flags |= POSIX_SPAWN_SETSIGDEF; if (reset_sigmask) flags |= POSIX_SPAWN_SETSIGMASK; if (desired_pgid.has_value()) flags |= POSIX_SPAWN_SETPGROUP; if (check_fail(posix_spawnattr_setflags(attr(), flags))) return; if (desired_pgid.has_value()) { if (check_fail(posix_spawnattr_setpgroup(attr(), *desired_pgid))) return; } // Everybody gets default handlers. if (reset_signal_handlers) { sigset_t sigdefault; get_signals_with_handlers(&sigdefault); if (check_fail(posix_spawnattr_setsigdefault(attr(), &sigdefault))) return; } // No signals blocked. if (reset_sigmask) { sigset_t sigmask; sigemptyset(&sigmask); blocked_signals_for_job(*j, &sigmask); if (check_fail(posix_spawnattr_setsigmask(attr(), &sigmask))) return; } // Apply our dup2s. for (const auto &act : dup2s.get_actions()) { if (act.target < 0) { if (check_fail(posix_spawn_file_actions_addclose(actions(), act.src))) return; } else { if (check_fail(posix_spawn_file_actions_adddup2(actions(), act.src, act.target))) return; } } } maybe_t posix_spawner_t::spawn(const char *cmd, char *const argv[], char *const envp[]) { if (get_error()) return none(); pid_t pid = -1; if (check_fail(posix_spawn(&pid, cmd, &*actions_, &*attr_, argv, envp))) { // The shebang wasn't introduced until UNIX Seventh Edition, so if // the kernel won't run the binary we hand it off to the intpreter // after performing a binary safety check, recommended by POSIX: a // line needs to exist before the first \0 with a lowercase letter if (error_ == ENOEXEC && is_thompson_shell_script(cmd)) { error_ = 0; // Create a new argv with /bin/sh prepended. std::vector argv2; char interp[] = _PATH_BSHELL; argv2.push_back(interp); // The command to call should use the full path, // not what we would pass as argv0. std::string cmd2 = cmd; argv2.push_back(&cmd2[0]); for (size_t i = 1; argv[i] != nullptr; i++) { argv2.push_back(argv[i]); } argv2.push_back(nullptr); if (check_fail(posix_spawn(&pid, interp, &*actions_, &*attr_, &argv2[0], envp))) { return none(); } } else { return none(); } } return pid; } #endif // FISH_USE_POSIX_SPAWN void safe_report_exec_error(int err, const char *actual_cmd, const char *const *argv, const char *const *envv) { switch (err) { case E2BIG: { char sz1[128]; long arg_max = -1; size_t sz = 0; size_t szenv = 0; const char *const *p; for (p = argv; *p; p++) { sz += std::strlen(*p) + 1; } for (p = envv; *p; p++) { szenv += std::strlen(*p) + 1; } sz += szenv; format_size_safe(sz1, sz); arg_max = sysconf(_SC_ARG_MAX); if (arg_max > 0) { if (sz >= static_cast(arg_max)) { char sz2[128]; format_size_safe(sz2, static_cast(arg_max)); FLOGF_SAFE( exec, "Failed to execute process '%s': the total size of the argument list and " "exported variables (%s) exceeds the OS limit of %s.", actual_cmd, sz1, sz2); } else { // MAX_ARG_STRLEN, a linux thing that limits the size of one argument. It's // defined in binfmts.h, but we don't want to include that just to be able to // print the real limit. FLOGF_SAFE(exec, "Failed to execute process '%s': An argument or exported variable " "exceeds the OS " "argument length limit.", actual_cmd); } if (szenv >= static_cast(arg_max) / 2) { FLOGF_SAFE(exec, "Hint: Your exported variables take up over half the limit. Try " "erasing or unexporting variables."); } } else { FLOGF_SAFE( exec, "Failed to execute process '%s': the total size of the argument list and " "exported variables (%s) exceeds the " "operating system limit.", actual_cmd, sz1); } break; } case ENOEXEC: { FLOGF_SAFE(exec, "Failed to execute process: '%s' the file could not be run by the " "operating system.", actual_cmd); char interpreter_buff[128] = {}; const char *interpreter = get_interpreter(actual_cmd, interpreter_buff, sizeof interpreter_buff); if (!interpreter) { // Paths ending in ".fish" need to start with a shebang if (const char *lastdot = strrchr(actual_cmd, '.')) { if (0 == strcmp(lastdot, ".fish")) { FLOGF_SAFE(exec, "fish scripts require an interpreter directive (must start with " "'#!/path/to/fish')."); } } } else { // If the shebang line exists, we would get an ENOENT or similar instead, // so I don't know how to reach this. FLOGF_SAFE(exec, "Maybe the interpreter directive (#! line) is broken?", actual_cmd); } break; } case ENOENT: { // ENOENT is returned by exec() when the path fails, but also returned by posix_spawn if // an open file action fails. These cases appear to be impossible to distinguish. We // address this by not using posix_spawn for file redirections, so all the ENOENTs we // find must be errors from exec(). char interpreter_buff[128] = {}; const char *interpreter = get_interpreter(actual_cmd, interpreter_buff, sizeof interpreter_buff); if (interpreter && 0 != access(interpreter, X_OK)) { // Detect Windows line endings and complain specifically about them. auto len = strlen(interpreter); if (len && interpreter[len - 1] == '\r') { FLOGF_SAFE(exec, "Failed to execute process '%s': The file uses Windows line " "endings (\\r\\n). Run dos2unix or similar to fix it.", actual_cmd); } else { FLOGF_SAFE(exec, "Failed to execute process '%s': The file specified the interpreter " "'%s', which is not an " "executable command.", actual_cmd, interpreter); } } else if (access(actual_cmd, X_OK) == 0) { FLOGF_SAFE(exec, "Failed to execute process '%s': The file exists and is executable. " "Check the interpreter or linker?", actual_cmd); } else { FLOGF_SAFE(exec, "Failed to execute process '%s': The file does not exist or could not " "be executed.", actual_cmd); } break; } case ENOMEM: { FLOGF_SAFE(exec, "Out of memory"); break; } case EACCES: { FLOGF_SAFE(exec, "Failed to execute process '%s': The file could not be accessed.", actual_cmd); break; } case ETXTBSY: { FLOGF_SAFE(exec, "Failed to execute process '%s': File is currently open for writing.", actual_cmd); break; } case ELOOP: { FLOGF_SAFE( exec, "Failed to execute process '%s': Too many layers of symbolic links. Maybe a loop?", actual_cmd); break; } case EINVAL: { FLOGF_SAFE(exec, "Failed to execute process '%s': Unsupported format.", actual_cmd); break; } case EISDIR: { FLOGF_SAFE(exec, "Failed to execute process '%s': File is a directory.", actual_cmd); break; } case ENOTDIR: { FLOGF_SAFE(exec, "Failed to execute process '%s': A path component is not a directory.", actual_cmd); break; } case EMFILE: { FLOGF_SAFE(exec, "Failed to execute process '%s': Too many open files in this process.", actual_cmd); break; } case ENFILE: { FLOGF_SAFE(exec, "Failed to execute process '%s': Too many open files on the system.", actual_cmd); break; } case ENAMETOOLONG: { FLOGF_SAFE(exec, "Failed to execute process '%s': Name is too long.", actual_cmd); break; } case EPERM: { FLOGF_SAFE(exec, "Failed to execute process '%s': No permission. Either suid/sgid is " "forbidden or you lack capabilities.", actual_cmd); break; } #ifdef EBADARCH case EBADARCH: { FLOGF_SAFE(exec, "Failed to execute process '%s': Bad CPU type in executable.", actual_cmd); break; } #endif default: { char errnum_buff[64]; format_long_safe(errnum_buff, err); FLOGF_SAFE(exec, "Failed to execute process '%s', unknown error number %s", actual_cmd, errnum_buff); break; } } } /// Returns the interpreter for the specified script. Returns NULL if file is not a script with a /// shebang. static char *get_interpreter(const char *command, char *buffer, size_t buff_size) { // OK to not use CLO_EXEC here because this is only called after fork. int fd = open(command, O_RDONLY); if (fd >= 0) { size_t idx = 0; while (idx + 1 < buff_size) { char ch; ssize_t amt = read(fd, &ch, sizeof ch); if (amt <= 0) break; if (ch == '\n') break; buffer[idx++] = ch; } buffer[idx++] = '\0'; close(fd); } if (std::strncmp(buffer, "#! /", const_strlen("#! /")) == 0) { return buffer + 3; } else if (std::strncmp(buffer, "#!/", const_strlen("#!/")) == 0) { return buffer + 2; } else if (std::strncmp(buffer, "#!", const_strlen("#!")) == 0) { // Relative path, basically always an issue. return buffer + 2; } return nullptr; };