Skip to content

Commit baa7b49

Browse files
committed
system.c: Avoid double close of file descriptor in child process.
Inside the child process, after we fork or clone, we close most file descriptors as is customary. If we are launching a container using clone, we already exempt procpipe[0] since we actually need that after we close the other file descriptors. In the case that fdout is -1 (consumer doesn't want the output), we create a dummy pipe that is used to receive output from the child and throw it away. As is customary, we immediately close the read end of the pipe in the child, since only the parent needs that. This wasn't being passed to cleanup_fds as an exemption, leading to a double close (and a valgrind warning that was non-fatal to the tests). Pass it in as exempted (since it was already closed) to avoid the double close and accompanying warning.
1 parent e3df64a commit baa7b49

1 file changed

Lines changed: 24 additions & 11 deletions

File tree

bbs/system.c

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -385,10 +385,10 @@ static void my_close_range(int start, int end)
385385
}
386386
#endif
387387

388-
static void cleanup_fds(int maxfd, int fdin, int fdout, int exclude)
388+
static void cleanup_fds(int maxfd, int fdin, int fdout, int exclude[2])
389389
{
390390
int minfd = 0;
391-
int exempt = 0;
391+
size_t exempt = 0;
392392
int fds[4];
393393

394394
/* Don't close these file descriptors */
@@ -398,19 +398,22 @@ static void cleanup_fds(int maxfd, int fdin, int fdout, int exclude)
398398
if (fdout >= 0 && fdout != fdin) { /* These are often the same, if so, no need to include twice */
399399
fds[exempt++] = fdout;
400400
}
401-
if (exclude >= 0) {
402-
fds[exempt++] = exclude;
401+
if (exclude[0] >= 0) {
402+
fds[exempt++] = exclude[0];
403+
}
404+
if (exclude[1] >= 0) {
405+
fds[exempt++] = exclude[1];
403406
}
404407

405408
/* Sort the file descriptors */
406-
qsort(fds, (size_t) exempt, sizeof(int), intsort);
409+
qsort(fds, exempt, sizeof(int), intsort);
407410

408411
#ifdef DEBUG_CHILD
409412
/* Leave STDIN/STDOUT/STDERR open for debugging messages */
410413
minfd = STDERR_FILENO + 1;
411414
#endif
412415

413-
child_debug(5, "Cleaning up file descriptors [%d, %d], %d exempt: %d, %d, %d\n", minfd, maxfd, exempt, fdin, fdout, exclude);
416+
child_debug(5, "Cleaning up file descriptors [%d, %d], %lu exempt: %d, %d, %d, %d\n", minfd, maxfd, exempt, fdin, fdout, exclude[0], exclude[1]);
414417

415418
/* Close all open file descriptors, so the child doesn't inherit any of them, except for node->slavefd
416419
* And yes, we close STDIN/STDOUT/STDERR as well since these refer to the sysop console, if there even is one.
@@ -427,13 +430,17 @@ static void cleanup_fds(int maxfd, int fdin, int fdout, int exclude)
427430
if (exempt > 2) {
428431
close_range(minfd, fds[2] - 1, 0);
429432
minfd = fds[2] + 1;
433+
if (exempt > 3) {
434+
close_range(minfd, fds[3] - 1, 0);
435+
minfd = fds[3] + 1;
436+
}
430437
}
431438
}
432439
}
433440
close_range(minfd, maxfd, 0);
434441
}
435442

436-
static int exec_pre(int fdin, int fdout, int exclude)
443+
static int exec_pre(int fdin, int fdout, int exclude[2])
437444
{
438445
struct rlimit rl;
439446

@@ -768,12 +775,13 @@ static ssize_t full_read(int fd, char *restrict buf, size_t len)
768775

769776
void bbs_child_exec_prep(int fdin, int fdout)
770777
{
778+
int exclude[2] = { -1, -1 };
771779
/* Do a subset of important things that we do in __bbs_execvpe */
772780
signal(SIGWINCH, SIG_DFL);
773781
signal(SIGTERM, SIG_DFL);
774782
signal(SIGINT, SIG_DFL);
775783
signal(SIGPIPE, SIG_DFL);
776-
exec_pre(fdin, fdout, -1);
784+
exec_pre(fdin, fdout, exclude);
777785
}
778786

779787
/*!
@@ -928,7 +936,11 @@ int __bbs_execvpe(struct bbs_node *node, struct bbs_exec_params *e, const char *
928936
}
929937
return -1;
930938
} else if (pid == 0) { /* Child */
931-
if (!e->isolated) {
939+
int exclude[2] = { -1, -1 };
940+
if (e->isolated) {
941+
/* If CLONE_CLEAR_SIGHAND was provided to clone, then the signal handlers didn't carry over, we're good. */
942+
exclude[0] = procpipe[0]; /* If we still need procpipe, don't close that */
943+
} else {
932944
/* Immediately install a dummy signal handler for SIGWINCH.
933945
* Until we call exec, the child retains the parent's signal handlers.
934946
* However, if we have a node, we immediately call bbs_node_update_winsize
@@ -954,14 +966,15 @@ int __bbs_execvpe(struct bbs_node *node, struct bbs_exec_params *e, const char *
954966
signal(SIGTERM, SIG_DFL);
955967
signal(SIGINT, SIG_DFL);
956968
signal(SIGPIPE, SIG_DFL);
957-
} /* else, if CLONE_CLEAR_SIGHAND was provided to clone, then the signal handlers didn't carry over, we're good. */
969+
}
958970

959971
if (fdout == -1) {
960972
bbs_std_close(pfd[0]); /* Close read end of pipe */
973+
exclude[1] = pfd[0]; /* Don't close this file descriptor again in close_range or valgrind will complain */
961974
fd = pfd[1]; /* Use write end of pipe */
962975
fdout = fd;
963976
}
964-
exec_pre(fdin, fdout, e->isolated ? procpipe[0] : -1); /* If we still need procpipe, don't close that */
977+
exec_pre(fdin, fdout, exclude);
965978
if (node && usenode) {
966979
/* Set controlling terminal, or otherwise shells don't fully work properly. */
967980
if (set_controlling_term(STDIN_FILENO)) { /* we dup2'd this to the slavefd. This is NOT the parent's STDIN. */

0 commit comments

Comments
 (0)