slirp: fork_exec(): create and connect child socket before fork()

Currently fork_exec() fork()s, and then creates and connects the
child socket which it uses for communication with the parent in
the child process. This is awkward because the child has no
mechanism to report failure back to the parent, which might end
up blocked forever in accept(). The child code also has an issue
pointed out by Coverity (CID 1005727), where if the qemu_socket()
call fails it will pass -1 as a file descriptor to connect().

Fix these issues by moving the creation of the child's end of
the socket to before the fork(), where we are in a position to
handle a possible failure.

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
This commit is contained in:
Peter Maydell 2018-11-06 15:13:23 +00:00 committed by Samuel Thibault
parent 4ded9bb54c
commit 5c75f3adbb
1 changed files with 34 additions and 21 deletions

View File

@ -85,9 +85,10 @@ fork_exec(struct socket *so, const char *ex, int do_pty)
int int
fork_exec(struct socket *so, const char *ex, int do_pty) fork_exec(struct socket *so, const char *ex, int do_pty)
{ {
int s; int s, cs;
struct sockaddr_in addr; struct sockaddr_in addr, csaddr;
socklen_t addrlen = sizeof(addr); socklen_t addrlen = sizeof(addr);
socklen_t csaddrlen = sizeof(csaddr);
int opt; int opt;
const char *argv[256]; const char *argv[256];
/* don't want to clobber the original */ /* don't want to clobber the original */
@ -120,10 +121,35 @@ fork_exec(struct socket *so, const char *ex, int do_pty)
} }
} }
if (getsockname(s, (struct sockaddr *)&csaddr, &csaddrlen) < 0) {
closesocket(s);
return 0;
}
cs = qemu_socket(AF_INET, SOCK_STREAM, 0);
if (cs < 0) {
closesocket(s);
return 0;
}
csaddr.sin_addr = loopback_addr;
/*
* This connect won't block because we've already listen()ed on
* the server end (even though we won't accept() the connection
* until later on).
*/
do {
ret = connect(cs, (struct sockaddr *)&csaddr, csaddrlen);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
closesocket(s);
closesocket(cs);
return 0;
}
pid = fork(); pid = fork();
switch(pid) { switch(pid) {
case -1: case -1:
error_report("Error: fork failed: %s", strerror(errno)); error_report("Error: fork failed: %s", strerror(errno));
closesocket(cs);
close(s); close(s);
return 0; return 0;
@ -131,21 +157,10 @@ fork_exec(struct socket *so, const char *ex, int do_pty)
setsid(); setsid();
/* Set the DISPLAY */ /* Set the DISPLAY */
getsockname(s, (struct sockaddr *)&addr, &addrlen);
close(s); close(s);
/* dup2(cs, 0);
* Connect to the socket dup2(cs, 1);
* XXX If any of these fail, we're in trouble! dup2(cs, 2);
*/
s = qemu_socket(AF_INET, SOCK_STREAM, 0);
addr.sin_addr = loopback_addr;
do {
ret = connect(s, (struct sockaddr *)&addr, addrlen);
} while (ret < 0 && errno == EINTR);
dup2(s, 0);
dup2(s, 1);
dup2(s, 2);
for (s = getdtablesize() - 1; s >= 3; s--) for (s = getdtablesize() - 1; s >= 3; s--)
close(s); close(s);
@ -178,12 +193,10 @@ fork_exec(struct socket *so, const char *ex, int do_pty)
default: default:
qemu_add_child_watch(pid); qemu_add_child_watch(pid);
closesocket(cs);
/* /*
* XXX this could block us... * This should never block, because we already connect()ed
* XXX Should set a timer here, and if accept() doesn't * on the child end before we forked.
* return after X seconds, declare it a failure
* The only reason this will block forever is if socket()
* of connect() fail in the child process
*/ */
do { do {
so->s = accept(s, (struct sockaddr *)&addr, &addrlen); so->s = accept(s, (struct sockaddr *)&addr, &addrlen);