Add an option to close all open file descriptors

This adds the minijail_close_open_fds() API to close all open file
descriptors (except the pipes that are internally set up to communicate
with the jailed process).

Bug: 32005517
Test: libminijail_unittest
Change-Id: Ia392f14c080716297c5766ad31af983ee6c5ead3
diff --git a/libminijail.c b/libminijail.c
index e837414..a44c5d2 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -9,6 +9,7 @@
 
 #include <asm/unistd.h>
 #include <ctype.h>
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <grp.h>
@@ -148,6 +149,7 @@
 		int cgroups:1;
 		int alt_syscall:1;
 		int reset_signal_mask:1;
+		int close_open_fds:1;
 	} flags;
 	uid_t uid;
 	gid_t gid;
@@ -461,6 +463,11 @@
 	j->flags.ns_cgroups = 1;
 }
 
+void API minijail_close_open_fds(struct minijail *j)
+{
+	j->flags.close_open_fds = 1;
+}
+
 void API minijail_remount_proc_readonly(struct minijail *j)
 {
 	j->flags.vfs = 1;
@@ -1796,6 +1803,43 @@
 	return dup2(fds[index], fd);
 }
 
+int close_open_fds(int *inheritable_fds, size_t size)
+{
+	const char *kFdPath = "/proc/self/fd";
+
+	DIR *d = opendir(kFdPath);
+	struct dirent *dir_entry;
+
+	if (d == NULL)
+		return -1;
+	int dir_fd = dirfd(d);
+	while ((dir_entry = readdir(d)) != NULL) {
+		size_t i;
+		char *end;
+		bool should_close = true;
+		const int fd = strtol(dir_entry->d_name, &end, 10);
+
+		if ((*end) != '\0') {
+			continue;
+		}
+		/*
+		 * We might have set up some pipes that we want to share with
+		 * the parent process, and should not be closed.
+		 */
+		for (i = 0; i < size; ++i) {
+			if (fd == inheritable_fds[i]) {
+				should_close = false;
+				break;
+			}
+		}
+		/* Also avoid closing the directory fd. */
+		if (should_close && fd != dir_fd)
+			close(fd);
+	}
+	closedir(d);
+	return 0;
+}
+
 int minijail_run_internal(struct minijail *j, const char *filename,
 			  char *const argv[], pid_t *pchild_pid,
 			  int *pstdin_fd, int *pstdout_fd, int *pstderr_fd,
@@ -2077,6 +2121,35 @@
 			pdie("sigprocmask failed");
 	}
 
+	if (j->flags.close_open_fds) {
+		const size_t kMaxInheritableFdsSize = 10;
+		int inheritable_fds[kMaxInheritableFdsSize];
+		size_t size = 0;
+		if (use_preload) {
+			inheritable_fds[size++] = pipe_fds[0];
+			inheritable_fds[size++] = pipe_fds[1];
+		}
+		if (sync_child) {
+			inheritable_fds[size++] = child_sync_pipe_fds[0];
+			inheritable_fds[size++] = child_sync_pipe_fds[1];
+		}
+		if (pstdin_fd) {
+			inheritable_fds[size++] = stdin_fds[0];
+			inheritable_fds[size++] = stdin_fds[1];
+		}
+		if (pstdout_fd) {
+			inheritable_fds[size++] = stdout_fds[0];
+			inheritable_fds[size++] = stdout_fds[1];
+		}
+		if (pstderr_fd) {
+			inheritable_fds[size++] = stderr_fds[0];
+			inheritable_fds[size++] = stderr_fds[1];
+		}
+
+		if (close_open_fds(inheritable_fds, size) < 0)
+			die("failed to close open file descriptors");
+	}
+
 	if (sync_child)
 		wait_for_parent_setup(child_sync_pipe_fds);