Skip to content

Commit 5356267

Browse files
ZmnSCPxjcdecker
authored andcommitted
*: Use new closefrom module from ccan.
This also inadvertently fixes a latent bug: before this patch, in the `subd` function in `lightningd/subd.c`, we would close `execfail[1]` *before* doing an `exec`. We use an EOF on `execfail[1]` as a signal that `exec` succeeded (the fd is marked CLOEXEC), and otherwise use it to pump `errno` to the parent. The intent is that this fd should be kept open until `exec`, at which point CLOEXEC triggers and close that fd and sends the EOF, *or* if `exec` fails we can send the `errno` to the parent process vua that pipe-end. However, in the previous version, we end up closing that fd *before* reaching `exec`, either in the loop which `dup2`s passed-in fds (by overwriting `execfail[1]` with a `dup2`) or in the "close everything" loop, which does not guard against `execfail[1]`, only `dev_disconnect_fd`.
1 parent 5a84abb commit 5356267

File tree

3 files changed

+95
-20
lines changed

3 files changed

+95
-20
lines changed

common/dev_disconnect.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "config.h"
2+
#include <ccan/closefrom/closefrom.h>
23
#include <ccan/err/err.h>
34
#include <common/dev_disconnect.h>
45
#include <common/status.h>
@@ -122,6 +123,8 @@ void dev_blackhole_fd(int fd)
122123
int i;
123124
struct stat st;
124125

126+
int maxfd;
127+
125128
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0)
126129
err(1, "dev_blackhole_fd: creating socketpair");
127130

@@ -130,12 +133,25 @@ void dev_blackhole_fd(int fd)
130133
err(1, "dev_blackhole_fd: forking");
131134
case 0:
132135
/* Close everything but the dev_disconnect_fd, the socket
133-
* which is pretending to be the peer, and stderr. */
134-
for (i = 0; i < sysconf(_SC_OPEN_MAX); i++)
136+
* which is pretending to be the peer, and stderr.
137+
* The "correct" way to do this would be to move the
138+
* fds we want to preserve to the low end (0, 1, 2...)
139+
* of the fd space and then just do a single closefrom
140+
* call, but dup2 could fail with ENFILE (which is a
141+
* *system*-level error, i.e. the entire system has too
142+
* many processes with open files) and we have no
143+
* convenient way to inform the parent of the error.
144+
* So loop until we reach whichever is higher of fds[0]
145+
* or dev_disconnect_fd, and *then* closefrom after that.
146+
*/
147+
maxfd = (fds[0] > dev_disconnect_fd) ? fds[0] :
148+
dev_disconnect_fd ;
149+
for (i = 0; i < maxfd; i++)
135150
if (i != fds[0]
136151
&& i != dev_disconnect_fd
137152
&& i != STDERR_FILENO)
138153
close(i);
154+
closefrom(maxfd + 1);
139155

140156
/* Close once dev_disconnect file is truncated. */
141157
for (;;) {

lightningd/lightningd.c

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
* in detail below.
4040
*/
4141
#include <ccan/array_size/array_size.h>
42+
#include <ccan/closefrom/closefrom.h>
4243
#include <ccan/opt/opt.h>
4344
#include <ccan/pipecmd/pipecmd.h>
4445
#include <ccan/read_write_all/read_write_all.h>
@@ -1182,15 +1183,11 @@ int main(int argc, char *argv[])
11821183

11831184
/* Were we supposed to restart ourselves? */
11841185
if (try_reexec) {
1185-
long max_fd;
1186-
11871186
/* Give a reasonable chance for the install to finish. */
11881187
sleep(5);
11891188

11901189
/* Close all filedescriptors except stdin/stdout/stderr */
1191-
max_fd = sysconf(_SC_OPEN_MAX);
1192-
for (int i = STDERR_FILENO+1; i < max_fd; i++)
1193-
close(i);
1190+
closefrom(STDERR_FILENO + 1);
11941191
execv(orig_argv[0], orig_argv);
11951192
err(1, "Failed to re-exec ourselves after version change");
11961193
}

lightningd/subd.c

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <ccan/closefrom/closefrom.h>
12
#include <ccan/err/err.h>
23
#include <ccan/io/fdpass/fdpass.h>
34
#include <ccan/mem/mem.h>
@@ -21,12 +22,53 @@
2122
static bool move_fd(int from, int to)
2223
{
2324
assert(from >= 0);
25+
26+
/* dup2 with same arguments may be a no-op, but
27+
* the later close would make the fd invalid.
28+
* Handle this edge case.
29+
*/
30+
if (from == to)
31+
return true;
32+
2433
if (dup2(from, to) == -1)
2534
return false;
35+
36+
/* dup2 does not duplicate flags, copy it here.
37+
* This should be benign; the only POSIX-defined
38+
* flag is FD_CLOEXEC, and we only use it rarely.
39+
*/
40+
if (fcntl(to, F_SETFD, fcntl(from, F_GETFD)) < 0)
41+
return false;
42+
2643
close(from);
2744
return true;
2845
}
2946

47+
/* Like the above, but move the fd from whatever it currently has
48+
* to any other unused fd number that is *not* its current value.
49+
*/
50+
static bool move_fd_any(int *fd)
51+
{
52+
int old_fd = *fd;
53+
int new_fd;
54+
55+
assert(old_fd >= 0);
56+
57+
if ((new_fd = dup(old_fd)) == -1)
58+
return false;
59+
60+
/* dup does not duplicate flags. */
61+
if (fcntl(new_fd, F_SETFD, fcntl(old_fd, F_GETFD)) < 0)
62+
return false;
63+
64+
close(old_fd);
65+
66+
*fd = new_fd;
67+
68+
assert(old_fd != *fd);
69+
return true;
70+
}
71+
3072
struct subd_req {
3173
struct list_node list;
3274

@@ -149,8 +191,7 @@ static int subd(const char *path, const char *name,
149191
goto close_execfail_fail;
150192

151193
if (childpid == 0) {
152-
int fdnum = 3, i, stdin_is_now = STDIN_FILENO;
153-
long max;
194+
int fdnum = 3, stdin_is_now = STDIN_FILENO;
154195
size_t num_args;
155196
char *args[] = { NULL, NULL, NULL, NULL, NULL };
156197

@@ -165,29 +206,50 @@ static int subd(const char *path, const char *name,
165206
goto child_errno_fail;
166207
}
167208

168-
// Move dev_disconnect_fd out the way.
169-
if (dev_disconnect_fd != -1) {
170-
if (!move_fd(dev_disconnect_fd, 101))
171-
goto child_errno_fail;
172-
dev_disconnect_fd = 101;
173-
}
174-
175209
/* Dup any extra fds up first. */
176210
while ((fd = va_arg(*ap, int *)) != NULL) {
177211
int actual_fd = *fd;
178212
/* If this were stdin, we moved it above! */
179213
if (actual_fd == STDIN_FILENO)
180214
actual_fd = stdin_is_now;
215+
216+
/* If we would overwrite important fds, move those. */
217+
if (fdnum == dev_disconnect_fd) {
218+
if (!move_fd_any(&dev_disconnect_fd))
219+
goto child_errno_fail;
220+
}
221+
if (fdnum == execfail[1]) {
222+
if (!move_fd_any(&execfail[1]))
223+
goto child_errno_fail;
224+
}
225+
181226
if (!move_fd(actual_fd, fdnum))
182227
goto child_errno_fail;
183228
fdnum++;
184229
}
185230

231+
/* Move dev_disconnect_fd *after* the extra fds above. */
232+
if (dev_disconnect_fd != -1) {
233+
/* Do not overwrite execfail[1]. */
234+
if (fdnum == execfail[1]) {
235+
if (!move_fd_any(&execfail[1]))
236+
goto child_errno_fail;
237+
}
238+
if (!move_fd(dev_disconnect_fd, fdnum))
239+
goto child_errno_fail;
240+
dev_disconnect_fd = fdnum;
241+
fdnum++;
242+
}
243+
244+
/* Move execfail[1] *after* the fds we will pass
245+
* to the subdaemon. */
246+
if (!move_fd(execfail[1], fdnum))
247+
goto child_errno_fail;
248+
execfail[1] = fdnum;
249+
fdnum++;
250+
186251
/* Make (fairly!) sure all other fds are closed. */
187-
max = sysconf(_SC_OPEN_MAX);
188-
for (i = fdnum; i < max; i++)
189-
if (i != dev_disconnect_fd)
190-
close(i);
252+
closefrom(fdnum);
191253

192254
num_args = 0;
193255
args[num_args++] = tal_strdup(NULL, path);

0 commit comments

Comments
 (0)