On 3/21/23 18:28, Eric Blake wrote:
> it is indeed a bug in busybox now that POSIX is moving towards
> standardizing realpath, so I've filed it:
>
https://bugs.busybox.net/show_bug.cgi?id=15466
I've found another busybox bug.
The "/bin/sh" utility is provided by busybox as well (via the usual
symlinking).
Per POSIX, if
execvp(file, { argv[0], argv[1], ..., NULL })
were to fail with -1/ENOEXEC, then execvp() must retry "as if" with
execv(<shell path>, { argv[0], file, argv[1], ..., NULL })
In other words, if direct execution of "file" failed because "file"
"has the appropriate access permission but has an unrecognized format", then
execvp() is required to try executing "file" as a shell script. For that,
<shell path> is left unspecified by POSIX, but the arguments of the shell are
specified:
- Argv[0] remains the same. That is, what we wanted "file" to know itself as,
is what we now want *the shell executable* to know itself as.
- argv[1] becomes "file" -- this is the script that the shell is supposed to
run.
- argv[2] and onwards become positional parameters $1, $2, ... for the shell script.
And the argv[0] specification is what's violated by busybox, because if argv[0] is
anything other than "sh", then the busybox binary doesn't recognize itself
as the shell!
The simplest way to demonstrate the bug is this:
bash-5.2$ ( exec -a foobar /bin/sh <<< "echo hello" )
foobar: applet not found
And then, another way to demonstrate the same busybox issue... lets us, in fact, discover
a musl bug in turn!!!
Consider the following C program (called "test-execvp.c"; the binary is called
"test-execvp"):
-------------
#include <stdio.h>
#include <unistd.h>
int main(void)
{
char *args[] = { "foobar", NULL };
execvp("hello.sh", args);
perror("execvp");
return 1;
}
-------------
The file "hello.sh" resides in the current directory (same directory where
"test-execvp" resides). Furthermore it has execute permission, and the following
contents:
-------
echo hello
-------
Now consider the following command (from bash):
$ PATH=.:$PATH test-execvp
What is supposed to happen is this:
(1) bash shall find test-execvp in the current directory per PATH,
(2) execvp() shall find "hello.sh" in the current directory per PATH,
(3) execvp() shall hit an internal failure -1/ENOEXEC,
(4) execvp() shall then invoke the shell (under an unspecified pathname),
(5) the shell shall get "foobar" for its argv[0], and "hello.sh" for
its argv[1]
(6) we shall see "hello" on the standard output.
That's exactly what happens on Linux/glibc. (Note: this result has absolutely nothing
to do with my execvpe() implementation, or libnbd in the first place.)
Now, according to my above description of the busybox bug, we're tempted to believe
that step (6) fails on Alpine Linux (using musl + busybox). We expect the busybox binary
to be launched, via the /bin/sh symlink, in step (4), and we expect it to fail after step
(5), due to it not recognizing "foobar" as an "applet name".
It turns out however that step (4) does not happen. musl does not handle ENOEXEC:
> bash-5.2$ PATH=.:$PATH test-execvp
> execvp: Exec format error
execvp() is not permitted by POSIX to fail with ENOEXEC. (Not considering the virtually
impossible situation when executing the system shell binary *itself* fails with ENOEXEC.)
I've checked the musl source too, at commit 7d756e1c04de ("dns: prefer monotonic
clock for timeouts", 2023-02-12). The execvp() implementation:
https://git.musl-libc.org/cgit/musl/tree/src/process/execvp.c
does not handle ENOEXEC; what's more, the entire tree only sets errno=ENOEXEC in
"ldso/dynlink.c".
;, musl issues
should be reported on the mailing list,
- on the mailing list, I've found two reports of this symptom, one from
2018, another from 2020:
So it turns out that my execvp[e]() implementation is not only
async-signal-safe, but mitigates a known bug in musl, too.
(BTW what Rich Felker seems to be missing, under the second link above,
is that execvp() is *NOT* required to be async-signal-safe (approx.
"safe to use from a vforked child"), so malloc() is fair game within
execvp(), even in case malloc() is also async-signal-UNSAFE in musl.)
Laszlo