[vte] spawn: Async-signal-safety fixes
- From: Christian Persch <chpe src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [vte] spawn: Async-signal-safety fixes
- Date: Sat, 18 Jul 2020 11:53:48 +0000 (UTC)
commit ed78b9e2ec47675a9dccf045caca4d7a0b6c9fe8
Author: Christian Persch <chpe src gnome org>
Date: Sat Jul 18 13:52:55 2020 +0200
spawn: Async-signal-safety fixes
Ported from https://gitlab.gnome.org/GNOME/glib/-/issues/2140
with some adjustments.
Fixes: https://gitlab.gnome.org/GNOME/vte/-/issues/263
src/missing.cc | 69 +++++++++++++++++++++++------
src/spawn.cc | 43 ++++++++++++++++--
src/spawn.hh | 11 ++---
src/vtepty.cc | 5 +--
src/vtespawn.cc | 133 ++++++++++++++++++++++++++++----------------------------
src/vtespawn.hh | 7 +--
6 files changed, 173 insertions(+), 95 deletions(-)
---
diff --git a/src/missing.cc b/src/missing.cc
index 0ca1da6e..7729943d 100644
--- a/src/missing.cc
+++ b/src/missing.cc
@@ -51,6 +51,9 @@ struct linux_dirent64
char d_name[]; /* Filename (null-terminated) */
};
+/* This function is called between fork and execve/_exit and so must be
+ * async-signal-safe; see man:signal-safety(7).
+ */
static int
filename_to_fd (const char *p)
{
@@ -64,7 +67,7 @@ filename_to_fd (const char *p)
while ((c = *p++) != '\0')
{
- if (!g_ascii_isdigit (c))
+ if (c < '0' || c > '9')
return -1;
c -= '0';
@@ -80,6 +83,56 @@ filename_to_fd (const char *p)
#endif /* __linux__ */
+/* This function is called between fork and execve/_exit and so must be
+ * async-signal-safe; see man:signal-safety(7).
+ */
+static int
+getrlimit_NOFILE_max(void)
+{
+#ifdef HAVE_SYS_RESOURCE_H
+ struct rlimit rlim;
+
+#ifdef __linux__
+ if (prlimit(0 /* this PID */, RLIMIT_NOFILE, nullptr, &rlim) == 0 &&
+ rlim.rlim_max != RLIM_INFINITY)
+ return rlim.rlim_max;
+
+ /* fallback */
+#endif /* __linux__ */
+
+#ifdef __GLIBC__
+ /* Use getrlimit() function provided by the system if it is known to be
+ * async-signal safe.
+ *
+ * According to the glibc manual, getrlimit is AS-safe.
+ */
+ if (getrlimit(RLIMIT_NOFILE, &rlim) == 0 &&
+ rlim.rlim_max != RLIM_INFINITY)
+ return rlim.rlim_max;
+
+ /* fallback */
+#endif
+
+#endif /* HAVE_SYS_RESOURCE_H */
+
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+ /* Use sysconf() function provided by the system if it is known to be
+ * async-signal safe.
+ */
+ auto const r = sysconf(_SC_OPEN_MAX);
+ if (r != -1)
+ return r;
+
+ /* fallback */
+#endif
+
+ /* Hardcoded fallback: the default process hard limit in Linux as of 2020 */
+ return 4096;
+}
+
+/* This function is called between fork and execve/_exit and so must be
+ * async-signal-safe; see man:signal-safety(7).
+ */
int
fdwalk(int (*cb)(void *data, int fd),
void *data)
@@ -88,14 +141,9 @@ fdwalk(int (*cb)(void *data, int fd),
* may be slow on non-Linux operating systems, especially on systems allowing
* very high number of open file descriptors.
*/
- int open_max;
int fd;
int res = 0;
-#ifdef 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 | O_CLOEXEC);
@@ -129,14 +177,7 @@ fdwalk(int (*cb)(void *data, int fd),
#endif
-#ifdef HAVE_SYS_RESOURCE_H
-
- if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY)
- open_max = rl.rlim_max;
- else
-#endif
- open_max = sysconf (_SC_OPEN_MAX);
-
+ auto const open_max = getrlimit_NOFILE_max();
for (fd = 0; fd < open_max; fd++)
if ((res = cb (data, fd)) != 0)
break;
diff --git a/src/spawn.cc b/src/spawn.cc
index 354af8fe..34e7e332 100644
--- a/src/spawn.cc
+++ b/src/spawn.cc
@@ -278,8 +278,28 @@ SpawnContext::prepare_environ()
m_envv = vte::glib::take_strv(merge_environ(m_envv.release(), m_cwd.get(), inherit_environ()));
}
+char const*
+SpawnContext::search_path() const noexcept
+{
+ auto const path = m_search_path ? g_environ_getenv(environ(), "PATH") : nullptr;
+ return path ? : "/bin:/usr/bin:.";
+}
+
+size_t
+SpawnContext::workbuf_size() const noexcept
+{
+ auto const path = search_path();
+ return std::max(path ? strlen(path) + strlen(arg0()) + 2 /* leading '/' plus NUL terminator */ : 0,
+ (g_strv_length(argv()) + 2) * sizeof(char*));
+}
+
+/* This function is called between fork and execve/_exit and so must be
+ * async-signal-safe; see man:signal-safety(7).
+ */
SpawnContext::ExecError
-SpawnContext::exec(vte::libc::FD& child_report_error_pipe_write) noexcept
+SpawnContext::exec(vte::libc::FD& child_report_error_pipe_write,
+ void* workbuf,
+ size_t workbufsize) noexcept
{
/* NOTE! This function must not rely on smart pointers to
* release their object, since the destructors are NOT run
@@ -449,7 +469,8 @@ SpawnContext::exec(vte::libc::FD& child_report_error_pipe_write) noexcept
argv(),
environ(),
search_path(),
- search_path_from_envp());
+ workbuf,
+ workbufsize);
/* If we get here, exec failed */
return ExecError::EXEC;
@@ -508,6 +529,18 @@ SpawnOperation::prepare(vte::glib::Error& error)
error))
return false;
+ /* allocate workbuf for SpawnContext::Exec() */
+ auto const workbufsize = context().workbuf_size();
+ auto workbuf = vte::glib::take_free_ptr(g_try_malloc(workbufsize));
+ if (!workbuf) {
+ auto errsv = vte::libc::ErrnoSaver{};
+ error.set(G_IO_ERROR,
+ g_io_error_from_errno(errsv),
+ "Failed to allocate workbuf: %s",
+ g_strerror(errsv));
+ return false;
+ }
+
/* Need to add the write end of the pipe to the FD map, so
* that the FD re-arranging code knows it needs to preserve
* the FD and not dup2 over it.
@@ -530,7 +563,11 @@ SpawnOperation::prepare(vte::glib::Error& error)
child_report_error_pipe_read.reset();
- auto const err = context().exec(child_report_error_pipe_write);
+ auto const err = context().exec(child_report_error_pipe_write,
+ workbuf.get(), workbufsize);
+
+ /* Manually free the workbuf */
+ g_free(workbuf.release());
/* If we get here, exec failed. Write the error to the pipe and exit. */
_vte_write_err(child_report_error_pipe_write.get(), int(err));
diff --git a/src/spawn.hh b/src/spawn.hh
index 3e12af3b..5623050f 100644
--- a/src/spawn.hh
+++ b/src/spawn.hh
@@ -59,7 +59,6 @@ private:
bool m_inherit_environ{true};
bool m_systemd_scope{true};
bool m_require_systemd_scope{false};
- bool m_search_path_from_envp{false};
bool m_search_path{false};
public:
@@ -149,7 +148,6 @@ public:
void set_no_systemd_scope() noexcept { m_systemd_scope = false; }
void set_require_systemd_scope() noexcept { m_require_systemd_scope = true; }
void set_search_path() noexcept { m_search_path = true; }
- void set_search_path_from_envp() noexcept { m_search_path_from_envp = true; }
auto arg0() const noexcept { return m_arg0.get(); }
auto argv() const noexcept { return m_argv.get(); }
@@ -163,8 +161,9 @@ public:
constexpr auto inherit_environ() const noexcept { return m_inherit_environ; }
constexpr auto systemd_scope() const noexcept { return m_systemd_scope; }
constexpr auto require_systemd_scope() const noexcept { return m_require_systemd_scope; }
- constexpr auto search_path() const noexcept { return m_search_path; }
- constexpr auto search_path_from_envp() const noexcept { return m_search_path_from_envp; }
+
+ char const* search_path() const noexcept;
+ size_t workbuf_size() const noexcept;
void prepare_environ();
@@ -181,7 +180,9 @@ public:
UNSET_CLOEXEC,
};
- ExecError exec(vte::libc::FD& child_report_error_pipe_write) noexcept;
+ ExecError exec(vte::libc::FD& child_report_error_pipe_write,
+ void* workbuf,
+ size_t workbufsize) noexcept;
}; // class SpawnContext
diff --git a/src/vtepty.cc b/src/vtepty.cc
index 230d5b63..4705ff9c 100644
--- a/src/vtepty.cc
+++ b/src/vtepty.cc
@@ -624,9 +624,8 @@ spawn_context_from_args(VtePty* pty,
context.set_fallback_cwd(g_get_home_dir());
context.set_child_setup(child_setup, child_setup_data, child_setup_data_destroy);
- if (spawn_flags & G_SPAWN_SEARCH_PATH_FROM_ENVP)
- context.set_search_path_from_envp();
- if (spawn_flags & G_SPAWN_SEARCH_PATH)
+ if ((spawn_flags & G_SPAWN_SEARCH_PATH_FROM_ENVP) ||
+ (spawn_flags & G_SPAWN_SEARCH_PATH))
context.set_search_path();
if (spawn_flags & G_SPAWN_FILE_AND_ARGV_ZERO)
diff --git a/src/vtespawn.cc b/src/vtespawn.cc
index cdedf0ac..9f01ac80 100644
--- a/src/vtespawn.cc
+++ b/src/vtespawn.cc
@@ -40,14 +40,19 @@
#include "missing.hh"
-static gssize
-write_all (int fd, gconstpointer vbuf, gsize to_write)
+/* This function is called between fork and execve/_exit and so must be
+ * async-signal-safe; see man:signal-safety(7).
+ */
+static ssize_t
+write_all (int fd,
+ void const* vbuf,
+ size_t to_write) noexcept
{
char *buf = (char *) vbuf;
while (to_write > 0)
{
- gssize count = write (fd, buf, to_write);
+ auto count = write(fd, buf, to_write);
if (count < 0)
{
if (errno != EINTR)
@@ -63,9 +68,12 @@ write_all (int fd, gconstpointer vbuf, gsize to_write)
return TRUE;
}
+/* This function is called between fork and execve/_exit and so must be
+ * async-signal-safe; see man:signal-safety(7).
+ */
void
_vte_write_err (int fd,
- int msg)
+ int msg) noexcept
{
int data[2] = {msg, errno};
@@ -74,46 +82,57 @@ _vte_write_err (int fd,
/* Based on execvp from GNU C Library */
-static void
-script_execute (const char *file,
- char **argv,
- char **envp)
+/* This function is called between fork and execve/_exit and so must be
+ * async-signal-safe; see man:signal-safety(7).
+ */
+/* Returns false if failing before execv(e), or true if failing after it. */
+static bool
+script_execute (char const* file,
+ char** argv,
+ char** envp,
+ void* workbuf,
+ size_t workbufsize) noexcept
{
/* Count the arguments. */
int argc = 0;
while (argv[argc])
++argc;
- /* Construct an argument list for the shell. */
- {
- char **new_argv;
-
- new_argv = g_new0 (char*, argc + 2); /* /bin/sh and NULL */
+ auto argv_buffer = reinterpret_cast<char**>(workbuf);
+ auto argv_buffer_len = workbufsize / sizeof(char*);
+ /* Construct an argument list for the shell. */
+ if (size_t(argc + 2) > argv_buffer_len) {
+ errno = ENOMEM;
+ return false;
+ }
- new_argv[0] = (char *) "/bin/sh";
- new_argv[1] = (char *) file;
- while (argc > 0)
- {
- new_argv[argc + 1] = argv[argc];
- --argc;
- }
+ argv_buffer[0] = (char *) "/bin/sh";
+ argv_buffer[1] = (char *) file;
+ while (argc > 0)
+ {
+ argv_buffer[argc + 1] = argv[argc];
+ --argc;
+ }
- /* Execute the shell. */
- if (envp)
- execve (new_argv[0], new_argv, envp);
- else
- execv (new_argv[0], new_argv);
+ /* Execute the shell. */
+ if (envp)
+ execve (argv_buffer[0], argv_buffer, envp);
+ else
+ execv (argv_buffer[0], argv_buffer);
- g_free (new_argv);
- }
+ return true;
}
+/* This function is called between fork and execve/_exit and so must be
+ * async-signal-safe; see man:signal-safety(7).
+ */
int
_vte_execute (const char *file,
char **argv,
char **envp,
- bool search_path,
- bool search_path_from_envp)
+ char const* search_path,
+ void* workbuf,
+ size_t workbufsize) noexcept
{
if (*file == '\0')
{
@@ -122,7 +141,7 @@ _vte_execute (const char *file,
return -1;
}
- if (!(search_path || search_path_from_envp) || strchr (file, '/') != NULL)
+ if (!search_path || strchr (file, '/') != nullptr)
{
/* Don't search when it contains a slash. */
if (envp)
@@ -131,40 +150,23 @@ _vte_execute (const char *file,
execv (file, argv);
if (errno == ENOEXEC)
- script_execute (file, argv, envp);
+ script_execute(file, argv, envp, workbuf, workbufsize);
}
else
{
- gboolean got_eacces = 0;
- const char *path, *p;
- char *name, *freeme;
- gsize len;
- gsize pathlen;
-
- path = NULL;
- if (search_path_from_envp)
- path = g_environ_getenv (envp, "PATH");
- if (search_path && path == NULL)
- path = g_getenv ("PATH");
-
- if (path == NULL)
- {
- /* There is no 'PATH' in the environment. The default
- * search path in libc is the current directory followed by
- * the path 'confstr' returns for '_CS_PATH'.
- */
+ auto got_eacces = false;
+ char const* path = search_path;
+ char const* p;
+ char* name = reinterpret_cast<char*>(workbuf);
- /* In GLib we put . last, for security, and don't use the
- * unportable confstr(); UNIX98 does not actually specify
- * what to search if PATH is unset. POSIX may, dunno.
- */
-
- path = "/bin:/usr/bin:.";
- }
+ auto const len = strlen(file) + 1;
+ auto const pathlen = strlen(path);
- len = strlen (file) + 1;
- pathlen = strlen (path);
- freeme = name = (char*)g_malloc (pathlen + len + 1);
+ if (workbufsize < pathlen + len + 1)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
/* Copy the file name at the top, including '\0' */
memcpy (name + pathlen + 1, file, len);
@@ -186,7 +188,7 @@ _vte_execute (const char *file,
*/
startp = name + 1;
else
- startp = (char*)memcpy (name - (p - path), path, p - path);
+ startp = reinterpret_cast<char*>(memcpy (name - (p - path), path, p - path));
/* Try to execute this name. If it works, execv will not return. */
if (envp)
@@ -194,8 +196,9 @@ _vte_execute (const char *file,
else
execv (startp, argv);
- if (errno == ENOEXEC)
- script_execute (startp, argv, envp);
+ if (errno == ENOEXEC &&
+ !script_execute(startp, argv, envp, workbuf, workbufsize))
+ return -1;
switch (errno)
{
@@ -206,8 +209,7 @@ _vte_execute (const char *file,
*/
got_eacces = TRUE;
- /* FALL THRU */
-
+ [[fallthrough]];
case ENOENT:
#ifdef ESTALE
case ESTALE:
@@ -234,7 +236,6 @@ _vte_execute (const char *file,
* something went wrong executing it; return the error to our
* caller.
*/
- g_free (freeme);
return -1;
}
}
@@ -246,8 +247,6 @@ _vte_execute (const char *file,
* error.
*/
errno = EACCES;
-
- g_free (freeme);
}
/* Return the error from the last attempt (probably ENOENT). */
diff --git a/src/vtespawn.hh b/src/vtespawn.hh
index 481929cf..5398385d 100644
--- a/src/vtespawn.hh
+++ b/src/vtespawn.hh
@@ -25,8 +25,9 @@
int _vte_execute(char const* file,
char** argv,
char** envp,
- bool search_path,
- bool search_path_from_envp);
+ char const* path_env,
+ void* workbuf,
+ size_t workbufsize) noexcept;
void _vte_write_err (int fd,
- int msg);
+ int msg) noexcept;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]