summaryrefslogtreecommitdiff
path: root/glib/gspawn.c
diff options
context:
space:
mode:
Diffstat (limited to 'glib/gspawn.c')
-rw-r--r--glib/gspawn.c322
1 files changed, 241 insertions, 81 deletions
diff --git a/glib/gspawn.c b/glib/gspawn.c
index 4e029eedf..bca37da3a 100644
--- a/glib/gspawn.c
+++ b/glib/gspawn.c
@@ -32,6 +32,7 @@
#include <string.h>
#include <stdlib.h> /* for fdwalk */
#include <dirent.h>
+#include <unistd.h>
#ifdef HAVE_SPAWN_H
#include <spawn.h>
@@ -69,10 +70,18 @@
#include "glibintl.h"
#include "glib-unix.h"
+#ifdef __APPLE__
+#include <libproc.h>
+#include <sys/proc_info.h>
+#endif
+
#define INHERITS_OR_NULL_STDIN (G_SPAWN_STDIN_FROM_DEV_NULL | G_SPAWN_CHILD_INHERITS_STDIN)
#define INHERITS_OR_NULL_STDOUT (G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_CHILD_INHERITS_STDOUT)
#define INHERITS_OR_NULL_STDERR (G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_CHILD_INHERITS_STDERR)
+#define IS_STD_FILENO(_fd) ((_fd >= STDIN_FILENO) && (_fd <= STDERR_FILENO))
+#define IS_VALID_FILENO(_fd) (_fd >= 0)
+
/* posix_spawn() is assumed the fastest way to spawn, but glibc's
* implementation was buggy before glibc 2.24, so avoid it on old versions.
*/
@@ -162,8 +171,6 @@ extern char **environ;
*/
-static gint safe_close (gint fd);
-
static gint g_execute (const gchar *file,
gchar **argv,
gchar **argv_buffer,
@@ -267,11 +274,9 @@ close_and_invalidate (gint *fd)
{
if (*fd < 0)
return;
- else
- {
- safe_close (*fd);
- *fd = -1;
- }
+
+ g_close (*fd, NULL);
+ *fd = -1;
}
/* Some versions of OS X define READ_OK in public headers */
@@ -1338,27 +1343,31 @@ dupfd_cloexec (int old_fd, int new_fd_min)
return fd;
}
-/* This function is called between fork() and exec() and hence must be
- * async-signal-safe (see signal-safety(7)). */
-static gint
-safe_close (gint fd)
-{
- gint ret;
-
- do
- ret = close (fd);
- while (ret < 0 && errno == EINTR);
-
- return ret;
-}
-
-/* This function is called between fork() and exec() and hence must be
- * async-signal-safe (see signal-safety(7)). */
+/* fdwalk()-compatible callback to close a fd for non-compliant
+ * implementations of fdwalk() that potentially pass already
+ * closed fds.
+ *
+ * It is not an error to pass an invalid fd to this function.
+ *
+ * This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)).
+ */
G_GNUC_UNUSED static int
-close_func (void *data, int fd)
+close_func_with_invalid_fds (void *data, int fd)
{
+ /* We use close and not g_close here because on some platforms, we
+ * don't know how to close only valid, open file descriptors, so we
+ * have to pass bad fds to close too. g_close warns if given a bad
+ * fd.
+ *
+ * This function returns no error, because there is nothing that the caller
+ * could do with that information. That is even the case for EINTR. See
+ * g_close() about the specialty of EINTR and why that is correct.
+ * If g_close() ever gets extended to handle EINTR specially, then this place
+ * should get updated to do the same handling.
+ */
if (fd >= GPOINTER_TO_INT (data))
- (void) safe_close (fd);
+ close (fd);
return 0;
}
@@ -1403,6 +1412,8 @@ filename_to_fd (const char *p)
}
#endif
+static int safe_fdwalk_with_invalid_fds (int (*cb)(void *data, int fd), void *data);
+
/* This function is called between fork() and exec() and hence must be
* async-signal-safe (see signal-safety(7)). */
static int
@@ -1418,18 +1429,14 @@ safe_fdwalk (int (*cb)(void *data, int fd), void *data)
return fdwalk (cb, data);
#else
/* Fallback implementation of fdwalk. It should be async-signal safe, but it
- * may be slow on non-Linux operating systems, especially on systems allowing
- * very high number of open file descriptors.
+ * may fail on non-Linux operating systems. See safe_fdwalk_with_invalid_fds
+ * for a slower alternative.
*/
- gint open_max = -1;
+
+#ifdef __linux__
gint fd;
gint res = 0;
-
-#if 0 && defined(HAVE_SYS_RESOURCE_H)
- struct rlimit rl;
-#endif
-#ifdef __linux__
/* Avoid use of opendir/closedir since these are not async-signal-safe. */
int dir_fd = open ("/proc/self/fd", O_RDONLY | O_DIRECTORY);
if (dir_fd >= 0)
@@ -1453,11 +1460,12 @@ safe_fdwalk (int (*cb)(void *data, int fd), void *data)
}
}
- safe_close (dir_fd);
+ g_close (dir_fd, NULL);
return res;
}
- /* If /proc is not mounted or not accessible we fall back to the old
+ /* If /proc is not mounted or not accessible we fail here and rely on
+ * safe_fdwalk_with_invalid_fds to fall back to the old
* rlimit trick. */
#endif
@@ -1473,6 +1481,8 @@ safe_fdwalk (int (*cb)(void *data, int fd), void *data)
* fcntl(fd, F_PREVFD)
* - return highest allocated file descriptor < fd.
*/
+ gint fd;
+ gint res = 0;
open_max = fcntl (INT_MAX, F_PREVFD); /* find the maximum fd */
if (open_max < 0) /* No open files */
@@ -1481,9 +1491,30 @@ safe_fdwalk (int (*cb)(void *data, int fd), void *data)
for (fd = -1; (fd = fcntl (fd, F_NEXTFD, open_max)) != -1; )
if ((res = cb (data, fd)) != 0 || fd == open_max)
break;
-#else
+
+ return res;
+#endif
+
+ return safe_fdwalk_with_invalid_fds (cb, data);
+#endif
+}
+
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
+static int
+safe_fdwalk_with_invalid_fds (int (*cb)(void *data, int fd), void *data)
+{
+ /* Fallback implementation of fdwalk. It should be async-signal safe, but it
+ * may be slow, especially on systems allowing very high number of open file
+ * descriptors.
+ */
+ gint open_max = -1;
+ gint fd;
+ gint res = 0;
#if 0 && defined(HAVE_SYS_RESOURCE_H)
+ struct rlimit rl;
+
/* Use getrlimit() function provided by the system if it is known to be
* async-signal safe.
*
@@ -1513,13 +1544,41 @@ safe_fdwalk (int (*cb)(void *data, int fd), void *data)
if (open_max < 0)
open_max = 4096;
+#if defined(__APPLE__)
+ /* proc_pidinfo isn't documented as async-signal-safe but looking at the implementation
+ * in the darwin tree here:
+ *
+ * https://opensource.apple.com/source/Libc/Libc-498/darwin/libproc.c.auto.html
+ *
+ * It's just a thin wrapper around a syscall, so it's probably okay.
+ */
+ {
+ char buffer[open_max * PROC_PIDLISTFD_SIZE];
+ ssize_t buffer_size;
+
+ buffer_size = proc_pidinfo (getpid (), PROC_PIDLISTFDS, 0, buffer, sizeof (buffer));
+
+ if (buffer_size > 0 &&
+ sizeof (buffer) >= (size_t) buffer_size &&
+ (buffer_size % PROC_PIDLISTFD_SIZE) == 0)
+ {
+ const struct proc_fdinfo *fd_info = (const struct proc_fdinfo *) buffer;
+ size_t number_of_fds = (size_t) buffer_size / PROC_PIDLISTFD_SIZE;
+
+ for (size_t i = 0; i < number_of_fds; i++)
+ if ((res = cb (data, fd_info[i].proc_fd)) != 0)
+ break;
+
+ return res;
+ }
+ }
+#endif
+
for (fd = 0; fd < open_max; fd++)
if ((res = cb (data, fd)) != 0)
break;
-#endif
return res;
-#endif
}
/* This function is called between fork() and exec() and hence must be
@@ -1527,6 +1586,8 @@ safe_fdwalk (int (*cb)(void *data, int fd), void *data)
static int
safe_fdwalk_set_cloexec (int lowfd)
{
+ int ret;
+
#if defined(HAVE_CLOSE_RANGE) && defined(CLOSE_RANGE_CLOEXEC)
/* close_range() is available in Linux since kernel 5.9, and on FreeBSD at
* around the same time. It was designed for use in async-signal-safe
@@ -1538,11 +1599,14 @@ safe_fdwalk_set_cloexec (int lowfd)
* Handle ENOSYS in case it’s supported in libc but not the kernel; if so,
* fall back to safe_fdwalk(). Handle EINVAL in case `CLOSE_RANGE_CLOEXEC`
* is not supported. */
- int ret = close_range (lowfd, G_MAXUINT, CLOSE_RANGE_CLOEXEC);
+ ret = close_range (lowfd, G_MAXUINT, CLOSE_RANGE_CLOEXEC);
if (ret == 0 || !(errno == ENOSYS || errno == EINVAL))
return ret;
#endif /* HAVE_CLOSE_RANGE */
- return safe_fdwalk (set_cloexec, GINT_TO_POINTER (lowfd));
+
+ ret = safe_fdwalk (set_cloexec, GINT_TO_POINTER (lowfd));
+
+ return ret;
}
/* This function is called between fork() and exec() and hence must be
@@ -1552,6 +1616,20 @@ safe_fdwalk_set_cloexec (int lowfd)
static int
safe_closefrom (int lowfd)
{
+ int ret;
+
+#if defined(HAVE_CLOSE_RANGE)
+ /* close_range() is available in Linux since kernel 5.9, and on FreeBSD at
+ * around the same time. It was designed for use in async-signal-safe
+ * situations: https://bugs.python.org/issue38061
+ *
+ * Handle ENOSYS in case it’s supported in libc but not the kernel; if so,
+ * fall back to safe_fdwalk(). */
+ ret = close_range (lowfd, G_MAXUINT, 0);
+ if (ret == 0 || errno != ENOSYS)
+ return ret;
+#endif /* HAVE_CLOSE_RANGE */
+
#if defined(__FreeBSD__) || defined(__OpenBSD__) || \
(defined(__sun__) && defined(F_CLOSEFROM))
/* Use closefrom function provided by the system if it is known to be
@@ -1583,19 +1661,9 @@ safe_closefrom (int lowfd)
*/
return fcntl (lowfd, F_CLOSEM);
#else
+ ret = safe_fdwalk (close_func_with_invalid_fds, GINT_TO_POINTER (lowfd));
-#if defined(HAVE_CLOSE_RANGE)
- /* close_range() is available in Linux since kernel 5.9, and on FreeBSD at
- * around the same time. It was designed for use in async-signal-safe
- * situations: https://bugs.python.org/issue38061
- *
- * Handle ENOSYS in case it’s supported in libc but not the kernel; if so,
- * fall back to safe_fdwalk(). */
- int ret = close_range (lowfd, G_MAXUINT, 0);
- if (ret == 0 || errno != ENOSYS)
- return ret;
-#endif /* HAVE_CLOSE_RANGE */
- return safe_fdwalk (close_func, GINT_TO_POINTER (lowfd));
+ return ret;
#endif
}
@@ -1615,6 +1683,30 @@ safe_dup2 (gint fd1, gint fd2)
/* This function is called between fork() and exec() and hence must be
* async-signal-safe (see signal-safety(7)). */
+static gboolean
+relocate_fd_out_of_standard_range (gint *fd)
+{
+ gint ret = -1;
+ const int min_fileno = STDERR_FILENO + 1;
+
+ do
+ ret = fcntl (*fd, F_DUPFD, min_fileno);
+ while (ret < 0 && errno == EINTR);
+
+ /* Note we don't need to close the old fd, because the caller is expected
+ * to close fds in the standard range itself.
+ */
+ if (ret >= min_fileno)
+ {
+ *fd = ret;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
static gint
safe_open (const char *path, gint mode)
{
@@ -1632,7 +1724,7 @@ enum
CHILD_CHDIR_FAILED,
CHILD_EXEC_FAILED,
CHILD_OPEN_FAILED,
- CHILD_DUP2_FAILED,
+ CHILD_DUPFD_FAILED,
CHILD_FORK_FAILED,
CHILD_CLOSE_FAILED,
};
@@ -1671,17 +1763,42 @@ do_exec (gint child_err_report_fd,
if (working_directory && chdir (working_directory) < 0)
write_err_and_exit (child_err_report_fd,
CHILD_CHDIR_FAILED);
-
- /* Redirect pipes as required */
- if (stdin_fd >= 0)
+
+ /* It's possible the caller assigned stdin to an fd with a
+ * file number that is supposed to be reserved for
+ * stdout or stderr.
+ *
+ * If so, move it up out of the standard range, so it doesn't
+ * cause a conflict.
+ */
+ if (IS_STD_FILENO (stdin_fd) && stdin_fd != STDIN_FILENO)
+ {
+ int old_fd = stdin_fd;
+
+ if (!relocate_fd_out_of_standard_range (&stdin_fd))
+ write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
+
+ if (stdout_fd == old_fd)
+ stdout_fd = stdin_fd;
+
+ if (stderr_fd == old_fd)
+ stderr_fd = stdin_fd;
+ }
+
+ /* Redirect pipes as required
+ *
+ * There are two cases where we don't need to do the redirection
+ * 1. Where the associated file descriptor is cleared/invalid
+ * 2. When the associated file descriptor is already given the
+ * correct file number.
+ */
+ if (IS_VALID_FILENO (stdin_fd) && stdin_fd != STDIN_FILENO)
{
if (safe_dup2 (stdin_fd, 0) < 0)
write_err_and_exit (child_err_report_fd,
- CHILD_DUP2_FAILED);
+ CHILD_DUPFD_FAILED);
- if (!((stdout_fd >= 0 || stdout_to_null) && stdin_fd == 1) &&
- !((stderr_fd >= 0 || stderr_to_null) && stdin_fd == 2))
- set_cloexec (GINT_TO_POINTER(0), stdin_fd);
+ set_cloexec (GINT_TO_POINTER(0), stdin_fd);
}
else if (!child_inherits_stdin)
{
@@ -1692,19 +1809,34 @@ do_exec (gint child_err_report_fd,
CHILD_OPEN_FAILED);
if (safe_dup2 (read_null, 0) < 0)
write_err_and_exit (child_err_report_fd,
- CHILD_DUP2_FAILED);
+ CHILD_DUPFD_FAILED);
close_and_invalidate (&read_null);
}
- if (stdout_fd >= 0)
+ /* Like with stdin above, it's possible the caller assigned
+ * stdout to an fd with a file number that's intruding on the
+ * standard range.
+ *
+ * If so, move it out of the way, too.
+ */
+ if (IS_STD_FILENO (stdout_fd) && stdout_fd != STDOUT_FILENO)
+ {
+ int old_fd = stdout_fd;
+
+ if (!relocate_fd_out_of_standard_range (&stdout_fd))
+ write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
+
+ if (stderr_fd == old_fd)
+ stderr_fd = stdout_fd;
+ }
+
+ if (IS_VALID_FILENO (stdout_fd) && stdout_fd != STDOUT_FILENO)
{
if (safe_dup2 (stdout_fd, 1) < 0)
write_err_and_exit (child_err_report_fd,
- CHILD_DUP2_FAILED);
+ CHILD_DUPFD_FAILED);
- if (!((stdin_fd >= 0 || !child_inherits_stdin) && stdout_fd == 0) &&
- !((stderr_fd >= 0 || stderr_to_null) && stdout_fd == 2))
- set_cloexec (GINT_TO_POINTER(0), stdout_fd);
+ set_cloexec (GINT_TO_POINTER(0), stdout_fd);
}
else if (stdout_to_null)
{
@@ -1714,19 +1846,29 @@ do_exec (gint child_err_report_fd,
CHILD_OPEN_FAILED);
if (safe_dup2 (write_null, 1) < 0)
write_err_and_exit (child_err_report_fd,
- CHILD_DUP2_FAILED);
+ CHILD_DUPFD_FAILED);
close_and_invalidate (&write_null);
}
- if (stderr_fd >= 0)
+ if (IS_STD_FILENO (stderr_fd) && stderr_fd != STDERR_FILENO)
+ {
+ if (!relocate_fd_out_of_standard_range (&stderr_fd))
+ write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
+ }
+
+ /* Like with stdin/stdout above, it's possible the caller assigned
+ * stderr to an fd with a file number that's intruding on the
+ * standard range.
+ *
+ * Make sure it's out of the way, also.
+ */
+ if (IS_VALID_FILENO (stderr_fd) && stderr_fd != STDERR_FILENO)
{
if (safe_dup2 (stderr_fd, 2) < 0)
write_err_and_exit (child_err_report_fd,
- CHILD_DUP2_FAILED);
+ CHILD_DUPFD_FAILED);
- if (!((stdin_fd >= 0 || !child_inherits_stdin) && stderr_fd == 0) &&
- !((stdout_fd >= 0 || stdout_to_null) && stderr_fd == 1))
- set_cloexec (GINT_TO_POINTER(0), stderr_fd);
+ set_cloexec (GINT_TO_POINTER(0), stderr_fd);
}
else if (stderr_to_null)
{
@@ -1736,7 +1878,7 @@ do_exec (gint child_err_report_fd,
CHILD_OPEN_FAILED);
if (safe_dup2 (write_null, 2) < 0)
write_err_and_exit (child_err_report_fd,
- CHILD_DUP2_FAILED);
+ CHILD_DUPFD_FAILED);
close_and_invalidate (&write_null);
}
@@ -1750,7 +1892,7 @@ do_exec (gint child_err_report_fd,
if (child_setup == NULL && n_fds == 0)
{
if (safe_dup2 (child_err_report_fd, 3) < 0)
- write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
+ write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
set_cloexec (GINT_TO_POINTER (0), 3);
if (safe_closefrom (4) < 0)
write_err_and_exit (child_err_report_fd, CHILD_CLOSE_FAILED);
@@ -1786,7 +1928,7 @@ do_exec (gint child_err_report_fd,
if (max_target_fd == G_MAXINT)
{
errno = EINVAL;
- write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
+ write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
}
/* If we're doing remapping fd assignments, we need to handle
@@ -1800,7 +1942,7 @@ do_exec (gint child_err_report_fd,
{
source_fds[i] = dupfd_cloexec (source_fds[i], max_target_fd + 1);
if (source_fds[i] < 0)
- write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
+ write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
}
}
@@ -1822,11 +1964,11 @@ do_exec (gint child_err_report_fd,
{
child_err_report_fd = dupfd_cloexec (child_err_report_fd, max_target_fd + 1);
if (child_err_report_fd < 0)
- write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
+ write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
}
if (safe_dup2 (source_fds[i], target_fds[i]) < 0)
- write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
+ write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
close_and_invalidate (&source_fds[i]);
}
@@ -2192,6 +2334,9 @@ fork_exec (gboolean intermediate_child,
{
if (!g_unix_open_pipe (stdin_pipe, pipe_flags, error))
goto cleanup_and_fail;
+ if (_g_spawn_invalid_source_fd (stdin_pipe[0], source_fds, n_fds, error) ||
+ _g_spawn_invalid_source_fd (stdin_pipe[1], source_fds, n_fds, error))
+ goto cleanup_and_fail;
child_close_fds[n_child_close_fds++] = stdin_pipe[1];
stdin_fd = stdin_pipe[0];
}
@@ -2200,6 +2345,9 @@ fork_exec (gboolean intermediate_child,
{
if (!g_unix_open_pipe (stdout_pipe, pipe_flags, error))
goto cleanup_and_fail;
+ if (_g_spawn_invalid_source_fd (stdout_pipe[0], source_fds, n_fds, error) ||
+ _g_spawn_invalid_source_fd (stdout_pipe[1], source_fds, n_fds, error))
+ goto cleanup_and_fail;
child_close_fds[n_child_close_fds++] = stdout_pipe[0];
stdout_fd = stdout_pipe[1];
}
@@ -2208,6 +2356,9 @@ fork_exec (gboolean intermediate_child,
{
if (!g_unix_open_pipe (stderr_pipe, pipe_flags, error))
goto cleanup_and_fail;
+ if (_g_spawn_invalid_source_fd (stderr_pipe[0], source_fds, n_fds, error) ||
+ _g_spawn_invalid_source_fd (stderr_pipe[1], source_fds, n_fds, error))
+ goto cleanup_and_fail;
child_close_fds[n_child_close_fds++] = stderr_pipe[0];
stderr_fd = stderr_pipe[1];
}
@@ -2349,9 +2500,18 @@ fork_exec (gboolean intermediate_child,
if (!g_unix_open_pipe (child_err_report_pipe, pipe_flags, error))
goto cleanup_and_fail;
-
- if (intermediate_child && !g_unix_open_pipe (child_pid_report_pipe, pipe_flags, error))
+ if (_g_spawn_invalid_source_fd (child_err_report_pipe[0], source_fds, n_fds, error) ||
+ _g_spawn_invalid_source_fd (child_err_report_pipe[1], source_fds, n_fds, error))
goto cleanup_and_fail;
+
+ if (intermediate_child)
+ {
+ if (!g_unix_open_pipe (child_pid_report_pipe, pipe_flags, error))
+ goto cleanup_and_fail;
+ if (_g_spawn_invalid_source_fd (child_pid_report_pipe[0], source_fds, n_fds, error) ||
+ _g_spawn_invalid_source_fd (child_pid_report_pipe[1], source_fds, n_fds, error))
+ goto cleanup_and_fail;
+ }
pid = fork ();
@@ -2546,7 +2706,7 @@ fork_exec (gboolean intermediate_child,
g_strerror (buf[1]));
break;
- case CHILD_DUP2_FAILED:
+ case CHILD_DUPFD_FAILED:
g_set_error (error,
G_SPAWN_ERROR,
G_SPAWN_ERROR_FAILED,