1f3217
From a879d08e912a4421786b44af479f94f7b4503f5a Mon Sep 17 00:00:00 2001
1f3217
From: Philip Withnall <pwithnall@endlessos.org>
1f3217
Date: Mon, 17 Jan 2022 15:27:24 +0000
1f3217
Subject: [PATCH] gspawn: Report errors with closing file descriptors between
1f3217
 fork/exec
1f3217
MIME-Version: 1.0
1f3217
Content-Type: text/plain; charset=UTF-8
1f3217
Content-Transfer-Encoding: 8bit
1f3217
1f3217
If a seccomp policy is set up incorrectly so that it returns `EPERM` for
1f3217
`close_range()` rather than `ENOSYS` due to it not being recognised, no
1f3217
error would previously be reported from GLib, but some file descriptors
1f3217
wouldn’t be closed, and that would cause a hung zombie process. The
1f3217
zombie process would be waiting for one half of a socket to be closed.
1f3217
1f3217
Fix that by correctly propagating errors from `close_range()` back to the
1f3217
parent process so they can be reported correctly.
1f3217
1f3217
Distributions which aren’t yet carrying the Docker fix to correctly
1f3217
return `ENOSYS` from unrecognised syscalls may want to temporarily carry
1f3217
an additional patch to fall back to `safe_fdwalk()` if `close_range()`
1f3217
fails with `EPERM`. This change will not be accepted upstream as `EPERM`
1f3217
is not the right error for `close_range()` to be returning.
1f3217
1f3217
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
1f3217
1f3217
Fixes: #2580
1f3217
---
1f3217
 glib/gspawn.c | 35 ++++++++++++++++++++++++++---------
1f3217
 1 file changed, 26 insertions(+), 9 deletions(-)
1f3217
1f3217
diff --git a/glib/gspawn.c b/glib/gspawn.c
1f3217
index c2fe306dc..9c2f7ba7b 100644
1f3217
--- a/glib/gspawn.c
1f3217
+++ b/glib/gspawn.c
1f3217
@@ -1457,8 +1457,10 @@ safe_fdwalk (int (*cb)(void *data, int fd), void *data)
1f3217
 }
1f3217
 
1f3217
 /* This function is called between fork() and exec() and hence must be
1f3217
- * async-signal-safe (see signal-safety(7)). */
1f3217
-static void
1f3217
+ * async-signal-safe (see signal-safety(7)).
1f3217
+ *
1f3217
+ * On failure, `-1` will be returned and errno will be set. */
1f3217
+static int
1f3217
 safe_closefrom (int lowfd)
1f3217
 {
1f3217
 #if defined(__FreeBSD__) || defined(__OpenBSD__)
1f3217
@@ -1472,6 +1474,7 @@ safe_closefrom (int lowfd)
1f3217
    * should be safe to use.
1f3217
    */
1f3217
   (void) closefrom (lowfd);
1f3217
+  return 0;
1f3217
 #elif defined(__DragonFly__)
1f3217
   /* It is unclear whether closefrom function included in DragonFlyBSD libc_r
1f3217
    * is safe to use because it calls a lot of library functions. It is also
1f3217
@@ -1479,12 +1482,13 @@ safe_closefrom (int lowfd)
1f3217
    * direct system call here ourselves to avoid possible issues.
1f3217
    */
1f3217
   (void) syscall (SYS_closefrom, lowfd);
1f3217
+  return 0;
1f3217
 #elif defined(F_CLOSEM)
1f3217
   /* NetBSD and AIX have a special fcntl command which does the same thing as
1f3217
    * closefrom. NetBSD also includes closefrom function, which seems to be a
1f3217
    * simple wrapper of the fcntl command.
1f3217
    */
1f3217
-  (void) fcntl (lowfd, F_CLOSEM);
1f3217
+  return fcntl (lowfd, F_CLOSEM);
1f3217
 #else
1f3217
 
1f3217
 #if defined(HAVE_CLOSE_RANGE)
1f3217
@@ -1494,9 +1498,11 @@ safe_closefrom (int lowfd)
1f3217
    *
1f3217
    * Handle ENOSYS in case it’s supported in libc but not the kernel; if so,
1f3217
    * fall back to safe_fdwalk(). */
1f3217
-  if (close_range (lowfd, G_MAXUINT, 0) != 0 && errno == ENOSYS)
1f3217
+  int ret = close_range (lowfd, G_MAXUINT, 0);
1f3217
+  if (ret == 0 || errno != ENOSYS)
1f3217
+    return ret;
1f3217
 #endif  /* HAVE_CLOSE_RANGE */
1f3217
-  (void) safe_fdwalk (close_func, GINT_TO_POINTER (lowfd));
1f3217
+  return safe_fdwalk (close_func, GINT_TO_POINTER (lowfd));
1f3217
 #endif
1f3217
 }
1f3217
 
1f3217
@@ -1534,7 +1540,8 @@ enum
1f3217
   CHILD_EXEC_FAILED,
1f3217
   CHILD_OPEN_FAILED,
1f3217
   CHILD_DUP2_FAILED,
1f3217
-  CHILD_FORK_FAILED
1f3217
+  CHILD_FORK_FAILED,
1f3217
+  CHILD_CLOSE_FAILED,
1f3217
 };
1f3217
 
1f3217
 /* This function is called between fork() and exec() and hence must be
1f3217
@@ -1650,12 +1657,14 @@ do_exec (gint                  child_err_report_fd,
1f3217
           if (safe_dup2 (child_err_report_fd, 3) < 0)
1f3217
             write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
1f3217
           set_cloexec (GINT_TO_POINTER (0), 3);
1f3217
-          safe_closefrom (4);
1f3217
+          if (safe_closefrom (4) < 0)
1f3217
+            write_err_and_exit (child_err_report_fd, CHILD_CLOSE_FAILED);
1f3217
           child_err_report_fd = 3;
1f3217
         }
1f3217
       else
1f3217
         {
1f3217
-          safe_fdwalk (set_cloexec, GINT_TO_POINTER (3));
1f3217
+          if (safe_fdwalk (set_cloexec, GINT_TO_POINTER (3)) < 0)
1f3217
+            write_err_and_exit (child_err_report_fd, CHILD_CLOSE_FAILED);
1f3217
         }
1f3217
     }
1f3217
   else
1f3217
@@ -2446,7 +2455,15 @@ fork_exec (gboolean              intermediate_child,
1f3217
                            _("Failed to fork child process (%s)"),
1f3217
                            g_strerror (buf[1]));
1f3217
               break;
1f3217
-              
1f3217
+
1f3217
+            case CHILD_CLOSE_FAILED:
1f3217
+              g_set_error (error,
1f3217
+                           G_SPAWN_ERROR,
1f3217
+                           G_SPAWN_ERROR_FAILED,
1f3217
+                           _("Failed to close file descriptor for child process (%s)"),
1f3217
+                           g_strerror (buf[1]));
1f3217
+              break;
1f3217
+
1f3217
             default:
1f3217
               g_set_error (error,
1f3217
                            G_SPAWN_ERROR,
1f3217
-- 
1f3217
2.34.1
1f3217