posix: Fix pidfd_spawn/pidfd_spawnp leak if execve fails (BZ 31695)

If the pidfd_spawn/pidfd_spawnp helper process succeeds, but evecve
fails for some reason (either with an invalid/non-existent, memory
allocation, etc.) the resulting pidfd is never closed, nor returned
to caller (so it can call close).

Since the process creation failed, it should be up to posix_spawn to
also, close the file descriptor in this case (similar to what it
does to reap the process).

This patch also changes the waitpid with waitid (P_PIDFD) for pidfd
case, to avoid a possible pid re-use.

Checked on x86_64-linux-gnu.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
This commit is contained in:
Adhemerval Zanella 2024-05-06 13:20:56 -03:00
parent 17a293c5fa
commit c90cfce849
2 changed files with 58 additions and 33 deletions

View File

@ -26,6 +26,7 @@
#include <stdio.h> #include <stdio.h>
#include <support/check.h> #include <support/check.h>
#include <support/descriptors.h>
#include <tst-spawn.h> #include <tst-spawn.h>
int int
@ -38,38 +39,53 @@ do_test (void)
char * const args[] = { 0 }; char * const args[] = { 0 };
PID_T_TYPE pid = -1; PID_T_TYPE pid = -1;
int ret = POSIX_SPAWN (&pid, program, 0, 0, args, environ); {
if (ret != ENOENT) struct support_descriptors *descrs = support_descriptors_list ();
{
errno = ret;
FAIL_EXIT1 ("posix_spawn: %m");
}
/* POSIX states the value returned on pid variable in case of an error int ret = POSIX_SPAWN (&pid, program, 0, 0, args, environ);
is not specified. GLIBC will update the value iff the child if (ret != ENOENT)
execution is successful. */ {
if (pid != -1) errno = ret;
FAIL_EXIT1 ("posix_spawn returned pid != -1 (%i)", (int) pid); FAIL_EXIT1 ("posix_spawn: %m");
}
/* Check if no child is actually created. */ /* POSIX states the value returned on pid variable in case of an error
TEST_COMPARE (WAITID (P_ALL, 0, NULL, WEXITED), -1); is not specified. GLIBC will update the value iff the child
TEST_COMPARE (errno, ECHILD); execution is successful. */
if (pid != -1)
FAIL_EXIT1 ("posix_spawn returned pid != -1 (%i)", (int) pid);
/* Same as before, but with posix_spawnp. */ /* Check if no child is actually created. */
char *args2[] = { (char*) program, 0 }; TEST_COMPARE (WAITID (P_ALL, 0, NULL, WEXITED), -1);
TEST_COMPARE (errno, ECHILD);
ret = POSIX_SPAWNP (&pid, args2[0], 0, 0, args2, environ); /* Also check if there is no leak descriptors. */
if (ret != ENOENT) support_descriptors_check (descrs);
{ support_descriptors_free (descrs);
errno = ret; }
FAIL_EXIT1 ("posix_spawnp: %m");
}
if (pid != -1) {
FAIL_EXIT1 ("posix_spawnp returned pid != -1 (%i)", (int) pid); /* Same as before, but with posix_spawnp. */
char *args2[] = { (char*) program, 0 };
TEST_COMPARE (WAITID (P_ALL, 0, NULL, WEXITED), -1); struct support_descriptors *descrs = support_descriptors_list ();
TEST_COMPARE (errno, ECHILD);
int ret = POSIX_SPAWNP (&pid, args2[0], 0, 0, args2, environ);
if (ret != ENOENT)
{
errno = ret;
FAIL_EXIT1 ("posix_spawnp: %m");
}
if (pid != -1)
FAIL_EXIT1 ("posix_spawnp returned pid != -1 (%i)", (int) pid);
TEST_COMPARE (WAITID (P_ALL, 0, NULL, WEXITED), -1);
TEST_COMPARE (errno, ECHILD);
support_descriptors_check (descrs);
support_descriptors_free (descrs);
}
return 0; return 0;
} }

View File

@ -449,13 +449,22 @@ __spawnix (int *pid, const char *file,
caller to actually collect it. */ caller to actually collect it. */
ec = args.err; ec = args.err;
if (ec > 0) if (ec > 0)
/* There still an unlikely case where the child is cancelled after {
setting args.err, due to a positive error value. Also there is /* There still an unlikely case where the child is cancelled after
possible pid reuse race (where the kernel allocated the same pid setting args.err, due to a positive error value. Also there is
to an unrelated process). Unfortunately due synchronization possible pid reuse race (where the kernel allocated the same pid
issues where the kernel might not have the process collected to an unrelated process). Unfortunately due synchronization
the waitpid below can not use WNOHANG. */ issues where the kernel might not have the process collected
__waitpid (new_pid, NULL, 0); the waitpid below can not use WNOHANG. */
__waitid (use_pidfd ? P_PIDFD : P_PID,
use_pidfd ? args.pidfd : new_pid,
NULL,
WEXITED);
/* For pidfd we need to also close the file descriptor for the case
where execve fails. */
if (use_pidfd)
__close_nocancel_nostatus (args.pidfd);
}
} }
else else
ec = errno; ec = errno;