Fix leakage of placeholder descriptors to tracees

As a side effect of commit v4.11-211-g0736d4e, strace used to leak
placeholders for standard descriptors to tracees thus affecting their
behaviour.  Fix this by setting close-on-exec flag on placeholder
descriptors.

* strace.c (open_dummy_desc): Set close-on-exec flag on the descriptor
that is going to be returned to the caller.
(fd_is_placeholder): New array.
(ensure_standard_fds_opened, redirect_standard_fds): New functions.
(startup_child): Use redirect_standard_fds.
(init): Use ensure_standard_fds_opened.
diff --git a/strace.c b/strace.c
index 680456b..0ee43cf 100644
--- a/strace.c
+++ b/strace.c
@@ -1221,6 +1221,12 @@
 	perror_msg_and_die("exec");
 }
 
+/*
+ * Open a dummy descriptor for use as a placeholder.
+ * The descriptor is O_RDONLY with FD_CLOEXEC flag set.
+ * A read attempt from such descriptor ends with EOF,
+ * a write attempt is rejected with EBADF.
+ */
 static int
 open_dummy_desc(void)
 {
@@ -1229,9 +1235,56 @@
 	if (pipe(fds))
 		perror_msg_and_die("pipe");
 	close(fds[1]);
+	set_cloexec_flag(fds[0]);
 	return fds[0];
 }
 
+/* placeholder fds status for stdin and stdout */
+static bool fd_is_placeholder[2];
+
+/*
+ * Ensure that all standard file descriptors are open by opening placeholder
+ * file descriptors for those standard file descriptors that are not open.
+ *
+ * The information which descriptors have been made open is saved
+ * in fd_is_placeholder for later use.
+ */
+static void
+ensure_standard_fds_opened(void)
+{
+	int fd;
+
+	while ((fd = open_dummy_desc()) <= 2) {
+		if (fd == 2)
+			break;
+		fd_is_placeholder[fd] = true;
+	}
+
+	if (fd > 2)
+		close(fd);
+}
+
+/*
+ * Redirect stdin and stdout unless they have been opened earlier
+ * by ensure_standard_fds_opened as placeholders.
+ */
+static void
+redirect_standard_fds(void)
+{
+	int i;
+
+	/*
+	 * It might be a good idea to redirect stderr as well,
+	 * but we sometimes need to print error messages.
+	 */
+	for (i = 0; i <= 1; ++i) {
+		if (!fd_is_placeholder[i]) {
+			close(i);
+			open_dummy_desc();
+		}
+	}
+}
+
 static void
 startup_child(char **argv)
 {
@@ -1409,18 +1462,11 @@
 	 * the pipe is still open, it has a reader. Thus, "head" will not get its
 	 * SIGPIPE at once, on the first write.
 	 *
-	 * Preventing it by closing strace's stdin/out.
+	 * Preventing it by redirecting strace's stdin/out.
 	 * (Don't leave fds 0 and 1 closed, this is bad practice: future opens
 	 * will reuse them, unexpectedly making a newly opened object "stdin").
 	 */
-	close(0);
-	open_dummy_desc(); /* opens to fd#0 */
-	dup2(0, 1);
-#if 0
-	/* A good idea too, but we sometimes need to print error messages */
-	if (shared_log != stderr)
-		dup2(0, 2);
-#endif
+	redirect_standard_fds();
 }
 
 #if USE_SEIZE
@@ -1753,24 +1799,18 @@
 		error_msg("ptrace_setoptions = %#x", ptrace_setoptions);
 	test_ptrace_seize();
 
-	if (fcntl(0, F_GETFD) == -1 || fcntl(1, F_GETFD) == -1) {
-		/*
-		 * Something weird with our stdin and/or stdout -
-		 * for example, may be not open? In this case,
-		 * ensure that none of the future opens uses them.
-		 *
-		 * This was seen in the wild when /proc/sys/kernel/core_pattern
-		 * was set to "|/bin/strace -o/tmp/LOG PROG":
-		 * kernel runs coredump helper with fd#0 open but fd#1 closed (!),
-		 * therefore LOG gets opened to fd#1, and fd#1 is closed by
-		 * "don't hold up stdin/out open" code soon after.
-		 */
-		int fd = open_dummy_desc();
-		while (fd >= 0 && fd < 2)
-			fd = dup(fd);
-		if (fd > 2)
-			close(fd);
-	}
+	/*
+	 * Is something weird with our stdin and/or stdout -
+	 * for example, may they be not open? In this case,
+	 * ensure that none of the future opens uses them.
+	 *
+	 * This was seen in the wild when /proc/sys/kernel/core_pattern
+	 * was set to "|/bin/strace -o/tmp/LOG PROG":
+	 * kernel runs coredump helper with fd#0 open but fd#1 closed (!),
+	 * therefore LOG gets opened to fd#1, and fd#1 is closed by
+	 * "don't hold up stdin/out open" code soon after.
+	 */
+	ensure_standard_fds_opened();
 
 	/* Check if they want to redirect the output. */
 	if (outfname) {