fish-shell/src/postfork.cpp

458 lines
16 KiB
C++
Raw Normal View History

// Functions that we may safely call after fork().
#include "config.h" // IWYU pragma: keep
2015-07-25 23:14:25 +08:00
#include <errno.h>
#include <fcntl.h>
Allow more scripts without #! This change modifies the fish safety check surrounding execve / spawn so it can run shell scripts having concatenated binary content. We're using the same safety check as FreeBSD /bin/sh [1] and the Z-shell [5]. POSIX was recently revised to require this behavior: "The input file may be of any type, but the initial portion of the file intended to be parsed according to the shell grammar (XREF to XSH 2.10.2 Shell Grammar Rules) shall consist of characters and shall not contain the NUL character. The shell shall not enforce any line length limits." "Earlier versions of this standard required that input files to the shell be text files except that line lengths were unlimited. However, that was overly restrictive in relation to the fact that shells can parse a script without a trailing newline, and in relation to a common practice of concatenating a shell script ending with an 'exit' or 'exec $command' with a binary data payload to form a single-file self-extracting archive." [2] [3] One example use case of such scripts, is the Cosmopolitan C Library [4] which configuse the GNU Linker to output a polyglot shell+binary format that runs on Linux / Mac / Windows / FreeBSD / OpenBSD / NetBSD / BIOS. Fixes jart/cosmopolitan#88 [1] https://github.com/freebsd/freebsd-src/commit/9a1cd363318b7e9e70ef6af27d1675b371c16b1a [2] http://austingroupbugs.net/view.php?id=1250 [3] http://austingroupbugs.net/view.php?id=1226#c4394 [4] https://justine.lol/cosmopolitan/index.html [5] https://github.com/zsh-users/zsh/commit/326d9c203b3980c0f841bc62b06e37134c6e51ea
2021-03-09 08:29:45 +08:00
#include <paths.h>
2015-07-25 23:14:25 +08:00
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <cstring>
#include <memory>
#ifdef FISH_USE_POSIX_SPAWN
#include <spawn.h>
#endif
#include <cwchar>
2015-07-25 23:14:25 +08:00
#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
/// Fork error message.
#define FORK_ERROR "Could not create child process - exiting"
Allow more scripts without #! This change modifies the fish safety check surrounding execve / spawn so it can run shell scripts having concatenated binary content. We're using the same safety check as FreeBSD /bin/sh [1] and the Z-shell [5]. POSIX was recently revised to require this behavior: "The input file may be of any type, but the initial portion of the file intended to be parsed according to the shell grammar (XREF to XSH 2.10.2 Shell Grammar Rules) shall consist of characters and shall not contain the NUL character. The shell shall not enforce any line length limits." "Earlier versions of this standard required that input files to the shell be text files except that line lengths were unlimited. However, that was overly restrictive in relation to the fact that shells can parse a script without a trailing newline, and in relation to a common practice of concatenating a shell script ending with an 'exit' or 'exec $command' with a binary data payload to form a single-file self-extracting archive." [2] [3] One example use case of such scripts, is the Cosmopolitan C Library [4] which configuse the GNU Linker to output a polyglot shell+binary format that runs on Linux / Mac / Windows / FreeBSD / OpenBSD / NetBSD / BIOS. Fixes jart/cosmopolitan#88 [1] https://github.com/freebsd/freebsd-src/commit/9a1cd363318b7e9e70ef6af27d1675b371c16b1a [2] http://austingroupbugs.net/view.php?id=1250 [3] http://austingroupbugs.net/view.php?id=1226#c4394 [4] https://justine.lol/cosmopolitan/index.html [5] https://github.com/zsh-users/zsh/commit/326d9c203b3980c0f841bc62b06e37134c6e51ea
2021-03-09 08:29:45 +08:00
extern bool is_thompson_shell_script(const char *path);
static char *get_interpreter(const char *command, char *buffer, size_t buff_size);
2020-01-31 03:24:02 +08:00
/// 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) {
2020-01-31 03:24:02 +08:00
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);
2020-01-31 03:24:02 +08:00
narrow_string_safe(argv0, p->argv0());
narrow_string_safe(command, j->command_wcstr());
debug_safe(1, "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);
2020-01-31 03:24:02 +08:00
if (is_windows_subsystem_for_linux() && errno == EPERM) {
debug_safe(1,
"Please update to Windows 10 1809/17763 or higher to address known issues "
"with process groups and zombie processes.");
}
errno = err;
safe_perror("setpgid");
}
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) {
2020-01-31 03:24:02 +08:00
// 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.
debug_safe(2, "setpgid(2) returned EPERM. Retrying");
continue;
}
#ifdef __BSD__
// 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 new_termowner, pid_t fish_pgrp, 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) {
debug_safe(4, "redirect_in_child_after_fork failed in child_setup_process");
exit_without_destructors(1);
}
return err;
}
}
if (new_termowner != INVALID_PID && new_termowner != fish_pgrp) {
// 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.
if (tcgetpgrp(STDIN_FILENO) == fish_pgrp) {
// Ensure this doesn't send us to the background (see #5963)
signal(SIGTTIN, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
(void)tcsetpgrp(STDIN_FILENO, new_termowner);
}
}
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() {
ASSERT_IS_MAIN_THREAD();
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.
2017-07-30 01:40:07 +08:00
debug_safe(4, "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);
}
}
debug_safe(0, FORK_ERROR);
safe_perror("fork");
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<pid_t> desired_pgid = none();
if (auto job_pgid = j->group->get_pgid()) {
desired_pgid = *job_pgid;
} else {
assert(j->group->needs_pgid_assignment() && "We should be expecting a pgid");
// We are the first external proc in the job group. Set the desired_pgid to 0 to indicate we
// should creating a new process group.
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<pid_t> posix_spawner_t::spawn(const char *cmd, char *const argv[], char *const envp[]) {
if (get_error()) return none();
pid_t pid = -1;
Allow more scripts without #! This change modifies the fish safety check surrounding execve / spawn so it can run shell scripts having concatenated binary content. We're using the same safety check as FreeBSD /bin/sh [1] and the Z-shell [5]. POSIX was recently revised to require this behavior: "The input file may be of any type, but the initial portion of the file intended to be parsed according to the shell grammar (XREF to XSH 2.10.2 Shell Grammar Rules) shall consist of characters and shall not contain the NUL character. The shell shall not enforce any line length limits." "Earlier versions of this standard required that input files to the shell be text files except that line lengths were unlimited. However, that was overly restrictive in relation to the fact that shells can parse a script without a trailing newline, and in relation to a common practice of concatenating a shell script ending with an 'exit' or 'exec $command' with a binary data payload to form a single-file self-extracting archive." [2] [3] One example use case of such scripts, is the Cosmopolitan C Library [4] which configuse the GNU Linker to output a polyglot shell+binary format that runs on Linux / Mac / Windows / FreeBSD / OpenBSD / NetBSD / BIOS. Fixes jart/cosmopolitan#88 [1] https://github.com/freebsd/freebsd-src/commit/9a1cd363318b7e9e70ef6af27d1675b371c16b1a [2] http://austingroupbugs.net/view.php?id=1250 [3] http://austingroupbugs.net/view.php?id=1226#c4394 [4] https://justine.lol/cosmopolitan/index.html [5] https://github.com/zsh-users/zsh/commit/326d9c203b3980c0f841bc62b06e37134c6e51ea
2021-03-09 08:29:45 +08:00
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<char *> argv2;
Allow more scripts without #! This change modifies the fish safety check surrounding execve / spawn so it can run shell scripts having concatenated binary content. We're using the same safety check as FreeBSD /bin/sh [1] and the Z-shell [5]. POSIX was recently revised to require this behavior: "The input file may be of any type, but the initial portion of the file intended to be parsed according to the shell grammar (XREF to XSH 2.10.2 Shell Grammar Rules) shall consist of characters and shall not contain the NUL character. The shell shall not enforce any line length limits." "Earlier versions of this standard required that input files to the shell be text files except that line lengths were unlimited. However, that was overly restrictive in relation to the fact that shells can parse a script without a trailing newline, and in relation to a common practice of concatenating a shell script ending with an 'exit' or 'exec $command' with a binary data payload to form a single-file self-extracting archive." [2] [3] One example use case of such scripts, is the Cosmopolitan C Library [4] which configuse the GNU Linker to output a polyglot shell+binary format that runs on Linux / Mac / Windows / FreeBSD / OpenBSD / NetBSD / BIOS. Fixes jart/cosmopolitan#88 [1] https://github.com/freebsd/freebsd-src/commit/9a1cd363318b7e9e70ef6af27d1675b371c16b1a [2] http://austingroupbugs.net/view.php?id=1250 [3] http://austingroupbugs.net/view.php?id=1226#c4394 [4] https://justine.lol/cosmopolitan/index.html [5] https://github.com/zsh-users/zsh/commit/326d9c203b3980c0f841bc62b06e37134c6e51ea
2021-03-09 08:29:45 +08:00
char interp[] = _PATH_BSHELL;
argv2.push_back(interp);
for (size_t i = 0; argv[i] != nullptr; i++) {
argv2.push_back(argv[i]);
Allow more scripts without #! This change modifies the fish safety check surrounding execve / spawn so it can run shell scripts having concatenated binary content. We're using the same safety check as FreeBSD /bin/sh [1] and the Z-shell [5]. POSIX was recently revised to require this behavior: "The input file may be of any type, but the initial portion of the file intended to be parsed according to the shell grammar (XREF to XSH 2.10.2 Shell Grammar Rules) shall consist of characters and shall not contain the NUL character. The shell shall not enforce any line length limits." "Earlier versions of this standard required that input files to the shell be text files except that line lengths were unlimited. However, that was overly restrictive in relation to the fact that shells can parse a script without a trailing newline, and in relation to a common practice of concatenating a shell script ending with an 'exit' or 'exec $command' with a binary data payload to form a single-file self-extracting archive." [2] [3] One example use case of such scripts, is the Cosmopolitan C Library [4] which configuse the GNU Linker to output a polyglot shell+binary format that runs on Linux / Mac / Windows / FreeBSD / OpenBSD / NetBSD / BIOS. Fixes jart/cosmopolitan#88 [1] https://github.com/freebsd/freebsd-src/commit/9a1cd363318b7e9e70ef6af27d1675b371c16b1a [2] http://austingroupbugs.net/view.php?id=1250 [3] http://austingroupbugs.net/view.php?id=1226#c4394 [4] https://justine.lol/cosmopolitan/index.html [5] https://github.com/zsh-users/zsh/commit/326d9c203b3980c0f841bc62b06e37134c6e51ea
2021-03-09 08:29:45 +08:00
}
argv2.push_back(nullptr);
if (check_fail(posix_spawn(&pid, interp, &*actions_, &*attr_, &argv2[0], envp))) {
Allow more scripts without #! This change modifies the fish safety check surrounding execve / spawn so it can run shell scripts having concatenated binary content. We're using the same safety check as FreeBSD /bin/sh [1] and the Z-shell [5]. POSIX was recently revised to require this behavior: "The input file may be of any type, but the initial portion of the file intended to be parsed according to the shell grammar (XREF to XSH 2.10.2 Shell Grammar Rules) shall consist of characters and shall not contain the NUL character. The shell shall not enforce any line length limits." "Earlier versions of this standard required that input files to the shell be text files except that line lengths were unlimited. However, that was overly restrictive in relation to the fact that shells can parse a script without a trailing newline, and in relation to a common practice of concatenating a shell script ending with an 'exit' or 'exec $command' with a binary data payload to form a single-file self-extracting archive." [2] [3] One example use case of such scripts, is the Cosmopolitan C Library [4] which configuse the GNU Linker to output a polyglot shell+binary format that runs on Linux / Mac / Windows / FreeBSD / OpenBSD / NetBSD / BIOS. Fixes jart/cosmopolitan#88 [1] https://github.com/freebsd/freebsd-src/commit/9a1cd363318b7e9e70ef6af27d1675b371c16b1a [2] http://austingroupbugs.net/view.php?id=1250 [3] http://austingroupbugs.net/view.php?id=1226#c4394 [4] https://justine.lol/cosmopolitan/index.html [5] https://github.com/zsh-users/zsh/commit/326d9c203b3980c0f841bc62b06e37134c6e51ea
2021-03-09 08:29:45 +08:00
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) {
debug_safe(0, "Failed to execute process '%s'. Reason:", actual_cmd);
switch (err) {
case E2BIG: {
2012-11-19 16:31:03 +08:00
char sz1[128], sz2[128];
2012-11-19 16:31:03 +08:00
long arg_max = -1;
2012-11-19 16:31:03 +08:00
size_t sz = 0;
const char *const *p;
for (p = argv; *p; p++) {
sz += std::strlen(*p) + 1;
2012-11-19 16:31:03 +08:00
}
for (p = envv; *p; p++) {
sz += std::strlen(*p) + 1;
2012-11-19 16:31:03 +08:00
}
2012-11-19 16:31:03 +08:00
format_size_safe(sz1, sz);
arg_max = sysconf(_SC_ARG_MAX);
if (arg_max > 0) {
if (sz >= static_cast<unsigned long long>(arg_max)) {
format_size_safe(sz2, static_cast<unsigned long long>(arg_max));
debug_safe(
0,
"The total size of the argument and environment lists %s exceeds the "
"operating system limit of %s.",
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.
debug_safe(0,
"One of your arguments exceeds the operating system's argument "
"length limit.");
}
} else {
debug_safe(0,
"The total size of the argument and environment lists (%s) exceeds the "
"operating system limit.",
sz1);
2012-11-19 16:31:03 +08:00
}
2012-11-19 16:31:03 +08:00
debug_safe(0, "Try running the command again with fewer arguments.");
break;
}
case ENOEXEC: {
const char *err = safe_strerror(errno);
2012-11-19 16:31:03 +08:00
debug_safe(0, "exec: %s", err);
debug_safe(0,
"The file '%s' is marked as an executable but could not be run by the "
"operating system.",
actual_cmd);
2012-11-19 16:31:03 +08:00
break;
}
2012-11-19 16:31:03 +08:00
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') {
debug_safe(0,
2020-11-22 21:39:48 +08:00
"The file uses windows line endings (\\r\\n). Run dos2unix or "
"similar to fix it.");
} else {
debug_safe(0,
"The file '%s' specified the interpreter '%s', which is not an "
"executable command.",
actual_cmd, interpreter);
}
} else {
2012-11-19 16:31:03 +08:00
debug_safe(0, "The file '%s' does not exist or could not be executed.", actual_cmd);
}
break;
}
case ENOMEM: {
2012-11-19 16:31:03 +08:00
debug_safe(0, "Out of memory");
break;
}
default: {
const char *err = safe_strerror(errno);
2012-11-19 16:31:03 +08:00
debug_safe(0, "exec: %s", err);
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;
}
return nullptr;
};