[PATCH] posix: Fix pidfd_spawn/pidfd_spawnp leak if execve fails (BZ 31695)
Peter Cawley
corsix@corsix.org
Mon May 6 16:34:33 GMT 2024
RE the spawni.c change, should it use __close_nocancel rather than __close?
Cancellation should be disabled at this point in time due to earlier logic,
but __spawni_child still seems to err on the side of caution and
use __close_nocancel for the closing that it does, and it seems prudent to
be consistent.
On Mon, May 6, 2024 at 5:27 PM Adhemerval Zanella <
adhemerval.zanella@linaro.org> wrote:
> 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).
>
> Checked on x86_64-linux-gnu.
> ---
> posix/tst-spawn2.c | 80 +++++++++++++++++++-------------
> sysdeps/unix/sysv/linux/spawni.c | 20 +++++---
> 2 files changed, 61 insertions(+), 39 deletions(-)
>
> diff --git a/posix/tst-spawn2.c b/posix/tst-spawn2.c
> index bb507204a2..b2bad3f1f7 100644
> --- a/posix/tst-spawn2.c
> +++ b/posix/tst-spawn2.c
> @@ -26,6 +26,7 @@
> #include <stdio.h>
>
> #include <support/check.h>
> +#include <support/descriptors.h>
> #include <tst-spawn.h>
>
> int
> @@ -38,38 +39,53 @@ do_test (void)
> char * const args[] = { 0 };
> PID_T_TYPE pid = -1;
>
> - int ret = POSIX_SPAWN (&pid, program, 0, 0, args, environ);
> - if (ret != ENOENT)
> - {
> - errno = ret;
> - FAIL_EXIT1 ("posix_spawn: %m");
> - }
> -
> - /* POSIX states the value returned on pid variable in case of an error
> - is not specified. GLIBC will update the value iff the child
> - execution is successful. */
> - if (pid != -1)
> - FAIL_EXIT1 ("posix_spawn returned pid != -1 (%i)", (int) pid);
> -
> - /* Check if no child is actually created. */
> - TEST_COMPARE (WAITID (P_ALL, 0, NULL, WEXITED), -1);
> - TEST_COMPARE (errno, ECHILD);
> -
> - /* Same as before, but with posix_spawnp. */
> - char *args2[] = { (char*) program, 0 };
> -
> - 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);
> + {
> + struct support_descriptors *descrs = support_descriptors_list ();
> +
> + int ret = POSIX_SPAWN (&pid, program, 0, 0, args, environ);
> + if (ret != ENOENT)
> + {
> + errno = ret;
> + FAIL_EXIT1 ("posix_spawn: %m");
> + }
> +
> + /* POSIX states the value returned on pid variable in case of an error
> + is not specified. GLIBC will update the value iff the child
> + execution is successful. */
> + if (pid != -1)
> + FAIL_EXIT1 ("posix_spawn returned pid != -1 (%i)", (int) pid);
> +
> + /* Check if no child is actually created. */
> + TEST_COMPARE (WAITID (P_ALL, 0, NULL, WEXITED), -1);
> + TEST_COMPARE (errno, ECHILD);
> +
> + /* Also check if there is no leak descriptors. */
> + support_descriptors_check (descrs);
> + support_descriptors_free (descrs);
> + }
> +
> + {
> + /* Same as before, but with posix_spawnp. */
> + char *args2[] = { (char*) program, 0 };
> +
> + struct support_descriptors *descrs = support_descriptors_list ();
> +
> + 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;
> }
> diff --git a/sysdeps/unix/sysv/linux/spawni.c
> b/sysdeps/unix/sysv/linux/spawni.c
> index e8ed2babb9..ca540f11a2 100644
> --- a/sysdeps/unix/sysv/linux/spawni.c
> +++ b/sysdeps/unix/sysv/linux/spawni.c
> @@ -449,13 +449,19 @@ __spawnix (int *pid, const char *file,
> caller to actually collect it. */
> ec = args.err;
> 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
> - possible pid reuse race (where the kernel allocated the same pid
> - to an unrelated process). Unfortunately due synchronization
> - issues where the kernel might not have the process collected
> - the waitpid below can not use WNOHANG. */
> - __waitpid (new_pid, NULL, 0);
> + {
> + /* There still an unlikely case where the child is cancelled
> after
> + setting args.err, due to a positive error value. Also there
> is
> + possible pid reuse race (where the kernel allocated the same
> pid
> + to an unrelated process). Unfortunately due synchronization
> + issues where the kernel might not have the process collected
> + the waitpid below can not use WNOHANG. */
> + __waitpid (new_pid, NULL, 0);
> + /* For pidfd we need to also close the file descriptor for the
> case
> + where execve fails. */
> + if (use_pidfd)
> + __close (args.pidfd);
> + }
> }
> else
> ec = errno;
> --
> 2.43.0
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20240506/95cc87dd/attachment-0001.htm>
More information about the Libc-alpha
mailing list