posix_spawn - implement support for POSIX_SPAWN_CLOEXEC_DEFAULT

This new posix_spawn attribute flag marks all file descriptors
(except stdin/out/err) as close-on-exec before executing any user
registered file actions (posix_spawn_file_actions_addopen/adddup2).

Test: TreeHugger
Bug: 229913920
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: If458100d6a253a9b0348d4e93a9a610225f89615
diff --git a/libc/bionic/spawn.cpp b/libc/bionic/spawn.cpp
index 2067a1a..314a056 100644
--- a/libc/bionic/spawn.cpp
+++ b/libc/bionic/spawn.cpp
@@ -33,6 +33,7 @@
 #include <signal.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/resource.h>
 #include <unistd.h>
 
 #include <android/fdsan.h>
@@ -40,6 +41,25 @@
 #include "private/ScopedSignalBlocker.h"
 #include "private/SigSetConverter.h"
 
+static int set_cloexec(int i) {
+  int v = fcntl(i, F_GETFD);
+  if (v == -1) return -1;  // almost certainly: errno == EBADF
+  return fcntl(i, F_SETFD, v | FD_CLOEXEC);
+}
+
+// mark all open fds except stdin/out/err as close-on-exec
+static int cloexec_except_stdioe() {
+  // unfortunately getrlimit can lie:
+  // - both soft and hard limits can be lowered to 0, with fds still open, so it can underestimate
+  // - in practice it usually is some really large value (like 32K or more)
+  //   even though only a handful of small fds are actually open (ie. < 500),
+  //   this results in poor performance when trying to act on all possibly open fds
+  struct rlimit m;
+  int max = getrlimit(RLIMIT_NOFILE, &m) ? 1000000 : m.rlim_max;
+  for (int i = 3; i < max; ++i) set_cloexec(i);
+  return 0;
+}
+
 enum Action {
   kOpen,
   kClose,
@@ -141,6 +161,10 @@
   if ((flags & POSIX_SPAWN_SETSIGMASK) != 0) {
     if (sigprocmask64(SIG_SETMASK, &(*attr)->sigmask.sigset64, nullptr)) _exit(127);
   }
+
+  if ((flags & POSIX_SPAWN_CLOEXEC_DEFAULT) != 0) {
+    if (cloexec_except_stdioe()) _exit(127);
+  }
 }
 
 static int posix_spawn(pid_t* pid_ptr,
@@ -199,7 +223,7 @@
 int posix_spawnattr_setflags(posix_spawnattr_t* attr, short flags) {
   if ((flags & ~(POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF |
                  POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER |
-                 POSIX_SPAWN_USEVFORK | POSIX_SPAWN_SETSID)) != 0) {
+                 POSIX_SPAWN_USEVFORK | POSIX_SPAWN_SETSID | POSIX_SPAWN_CLOEXEC_DEFAULT)) != 0) {
     return EINVAL;
   }
   (*attr)->flags = flags;