[PATCH v2] posix: Fix pidfd_spawn/pidfd_spawnp leak if execve fails (BZ 31695)
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Mon May 20 17:22:46 GMT 2024
Indeed using waitid with P_PIDFD is the correct approach, I will update
the patch.
On 20/05/24 13:54, Peter Cawley wrote:
> For whatever it is worth, the patch looks good to me for addressing the reported bug.
>
> That said, as we're in the area, we could potentially go further and replace the __waitpid with a P_PIDFD waitid when we're in the use_pidfd case, thereby closing the pid reuse race described in the comment.
>
> On Mon, May 20, 2024 at 5:47 PM Adhemerval Zanella Netto <adhemerval.zanella@linaro.org <mailto:adhemerval.zanella@linaro.org>> wrote:
>
> Ping.
>
> On 06/05/24 14:27, Adhemerval Zanella 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.
> > --
> > Changes from v1:
> > - Use __close_nocancel_nostatus instead of __close.
> > ---
> > 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..1556dd17e0 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_nocancel_nostatus (args.pidfd);
> > + }
> > }
> > else
> > ec = errno;
>
More information about the Libc-alpha
mailing list