proc: Set close-on-exec flag on fork pipes

This commit is contained in:
Derek Parker 2016-04-11 08:59:15 -07:00 committed by Derek Parker
parent 26ce16f1db
commit 4fd02c829a
2 changed files with 31 additions and 17 deletions

@ -2,6 +2,14 @@
extern char** environ; extern char** environ;
int
close_exec_pipe(int fd[2]) {
if (pipe(fd) < 0) return -1;
if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) < 0) return -1;
if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) < 0) return -1;
return 0;
}
int int
fork_exec(char *argv0, char **argv, int size, fork_exec(char *argv0, char **argv, int size,
mach_port_name_t *task, mach_port_name_t *task,
@ -9,23 +17,23 @@ fork_exec(char *argv0, char **argv, int size,
mach_port_t *exception_port, mach_port_t *exception_port,
mach_port_t *notification_port) mach_port_t *notification_port)
{ {
// In order to call PT_SIGEXC below, we must ensure that we have acquired the mach task first. // Since we're using mach exceptions instead of signals,
// We facilitate this by creating a pipe and using it to let the forked process know that we've // we need to coordinate between parent and child via pipes
// finishing acquiring the mach task, and it can go ahead with the calls to PT_TRACE_ME and PT_SIGEXC. // to ensure that the parent has set the exception ports on
// the child task before it execs.
int fd[2]; int fd[2];
if (pipe(fd) < 0) return -1; if (close_exec_pipe(fd) < 0) return -1;
// Create another pipe so that we know when we're about to exec. This ensures that control only returns // Create another pipe to signal the parent on exec.
// back to Go-land when we call exec, effectively eliminating a race condition between launching the new int efd[2];
// process and trying to read its memory. if (close_exec_pipe(efd) < 0) return -1;
int wfd[2];
if (pipe(wfd) < 0) return -1;
kern_return_t kret; kern_return_t kret;
pid_t pid = fork(); pid_t pid = fork();
if (pid > 0) { if (pid > 0) {
// In parent. // In parent.
close(fd[0]); close(fd[0]);
close(efd[1]);
kret = acquire_mach_task(pid, task, port_set, exception_port, notification_port); kret = acquire_mach_task(pid, task, port_set, exception_port, notification_port);
if (kret != KERN_SUCCESS) return -1; if (kret != KERN_SUCCESS) return -1;
@ -33,10 +41,14 @@ fork_exec(char *argv0, char **argv, int size,
write(fd[1], &msg, 1); write(fd[1], &msg, 1);
close(fd[1]); close(fd[1]);
char w; char w;
read(wfd[0], &w, 1); size_t n = read(efd[0], &w, 1);
close(wfd[0]); close(efd[0]);
if (n != 0) {
// Child died, reap it.
waitpid(pid, NULL, 0);
return -1;
}
return pid; return pid;
} }
@ -64,13 +76,14 @@ fork_exec(char *argv0, char **argv, int size,
pret = ptrace(PT_SIGEXC, 0, 0, 0); pret = ptrace(PT_SIGEXC, 0, 0, 0);
if (pret != 0 && errno != 0) return -errno; if (pret != 0 && errno != 0) return -errno;
char msg = 'd';
write(wfd[1], &msg, 1);
close(wfd[1]);
// Create the child process. // Create the child process.
execve(argv0, argv, environ); execve(argv0, argv, environ);
// We should never reach here, but if we did something went wrong. // We should never reach here, but if we did something went wrong.
// Write a message to parent to alert that exec failed.
char msg = 'd';
write(efd[1], &msg, 1);
close(efd[1]);
exit(1); exit(1);
} }

@ -4,6 +4,7 @@
#include <sys/ptrace.h> #include <sys/ptrace.h>
#include <errno.h> #include <errno.h>
#include <stdlib.h> #include <stdlib.h>
#include <fcntl.h>
int int
fork_exec(char *, char **, int, mach_port_name_t*, mach_port_t*, mach_port_t*, mach_port_t*); fork_exec(char *, char **, int, mach_port_name_t*, mach_port_t*, mach_port_t*, mach_port_t*);