I don't have a reproducer, but by reading of the code it was clear
that write() and close() before error() could eventually clobber the
errno value from execvp().
Pushed the attached.
Have a nice day,
Berny
From dd594fe7b83f4d0096a3d9a903b3abe6a3f4fb2e Mon Sep 17 00:00:00 2001
From: Bernhard Voelker <[email protected]>
Date: Mon, 23 Feb 2026 23:02:08 +0100
Subject: [PATCH] xargs: make errno handling after execvp failure more robust
* xargs/xargs.c (xargs_do_exec): Save errno in a new variable to avoid
that write() or close() clobber it before its value is used in error().
---
xargs/xargs.c | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/xargs/xargs.c b/xargs/xargs.c
index 262a64cf..b7f8f1e8 100644
--- a/xargs/xargs.c
+++ b/xargs/xargs.c
@@ -1348,7 +1348,8 @@ xargs_do_exec (struct buildcmd_control *ctl, void *usercontext, int argc, char *
errno = E2BIG;
else
execvp (argv[0], argv);
- if (errno)
+ int saved_errno = errno;
+ if (saved_errno)
{
/* Write errno value to parent. We do this even if
* the error was not E2BIG, because we need to
@@ -1362,19 +1363,21 @@ xargs_do_exec (struct buildcmd_control *ctl, void *usercontext, int argc, char *
* utility if we run it, for POSIX compliance on the
* handling of exit values.
*/
- write (fd[1], &errno, sizeof (int));
+ write (fd[1], &saved_errno, sizeof (int));
}
close (fd[1]);
- if (E2BIG != errno)
+ if (E2BIG != saved_errno)
{
- error (0, errno, _("failed to run command %s"),
+ error (0, saved_errno, _("failed to run command %s"),
quotearg_n_style (0, locale_quoting_style, argv[0]));
}
/* The actual value returned here should be irrelevant,
* because the parent will test our value of errno.
*/
- _exit (errno == ENOENT ? XARGS_EXIT_COMMAND_NOT_FOUND : XARGS_EXIT_COMMAND_CANNOT_BE_RUN);
+ _exit (saved_errno == ENOENT
+ ? XARGS_EXIT_COMMAND_NOT_FOUND
+ : XARGS_EXIT_COMMAND_CANNOT_BE_RUN);
/*NOTREACHED*/
} /* child */
--
2.52.0