fdpass_recv: Set O_CLOEXEC on received fds

Turns out this is necessary even if the fd is going to be passed through
exec() soon, because the supervisor might receive multiple fds before
spawning another process, in which case all of them are going to be
passed to the new process instead of just one.
This commit is contained in:
Yorhel 2025-03-24 11:07:36 +01:00
parent 9e1be5bc71
commit 2f50736782
3 changed files with 9 additions and 13 deletions

13
FU.pm
View file

@ -475,26 +475,23 @@ sub _supervisor($c) {
} }
# Don't bother spawning more than 1 at a time while in error state # Don't bother spawning more than 1 at a time while in error state
my $spawn = !$err ? $c->{proc} - keys %childs : (grep $_ == 1, values %childs) ? 0 : 1; my $spawn = !$err ? $c->{proc} - keys %childs : !@client_fd && (grep $_ == 1, values %childs) ? 0 : 1;
for (1..$spawn) { for (1..$spawn) {
my $client = shift @client_fd; my $client = @client_fd ? IO::Socket->new_from_fd(shift(@client_fd), 'r') : undef;
my $pid = fork; my $pid = fork;
die $! if !defined $pid; die $! if !defined $pid;
if (!$pid) { # child if (!$pid) { # child
$SIG{CHLD} = $SIG{HUP} = $SIG{INT} = $SIG{TERM} = undef; $SIG{CHLD} = $SIG{HUP} = $SIG{INT} = $SIG{TERM} = undef;
# In error state, wait with loading the script until we've received a request.
# Otherwise we'll end up in an infinite spawning loop if the script doesn't start properly.
$client = $c->{listen_sock}->accept() or die $! if !$client && $err;
if ($client) { if ($client) {
$ENV{FU_CLIENT_FD} = $client;
} elsif ($err) {
# In error state, wait with loading the script until we've received a request.
# Otherwise we'll end up in an infinite spawning loop if the script doesn't start properly.
$client = $c->{listen_sock}->accept() or die $!;
fcntl $client, Fcntl::F_SETFD, 0; fcntl $client, Fcntl::F_SETFD, 0;
$ENV{FU_CLIENT_FD} = fileno $client; $ENV{FU_CLIENT_FD} = fileno $client;
} }
exec $^X, (map "-I$_", @INC), $0; exec $^X, (map "-I$_", @INC), $0;
exit 1; exit 1;
} }
$client && IO::Socket->new_from_fd($client, 'r'); # close() the fd if we have one
$childs{$pid} = 1; $childs{$pid} = 1;
} }

View file

@ -458,10 +458,9 @@ Like regular socket I/O, a single C<fdpass_send()> message may be split across
multiple C<fdpass_recv()> calls; in that case the C<$fd> is only received on multiple C<fdpass_recv()> calls; in that case the C<$fd> is only received on
the first call. the first call.
Don't use this function if the sender may include multiple file descriptors in The C<O_CLOEXEC> flag is set on received file descriptors. Don't use this
a single message, weird things can happen. File descriptors received this way function if the sender may include multiple file descriptors in a single
do not have the C<CLOEXEC> flag and will thus survive a call to C<exec()>. message, weird things can happen. Refer to L<this wonderful
Refer to L<this wonderful
discussion|https://gist.github.com/kentonv/bc7592af98c68ba2738f4436920868dc> discussion|https://gist.github.com/kentonv/bc7592af98c68ba2738f4436920868dc>
for more weirdness and edge cases. for more weirdness and edge cases.

View file

@ -53,7 +53,7 @@ static int fufdpass_recv(pTHX_ I32 ax, int socket, size_t len) {
msg.msg_controllen = sizeof(cmsgbuf.buf); msg.msg_controllen = sizeof(cmsgbuf.buf);
msg.msg_flags = 0; msg.msg_flags = 0;
ssize_t r = recvmsg(socket, &msg, 0); ssize_t r = recvmsg(socket, &msg, MSG_CMSG_CLOEXEC);
if (r < 0) { if (r < 0) {
ST(0) = &PL_sv_undef; ST(0) = &PL_sv_undef;
ST(1) = &PL_sv_undef; ST(1) = &PL_sv_undef;