On 3/22/23 15:45, Laszlo Ersek wrote: > 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:
It's getting crazier by the hour. I thought to create a reproducer for busybox, in spite of musl breaking down at an earlier point (see above). For that, I *statically linked* "test-execvp" on RHEL-9.1 (using glibc), and then executed the binary in the Alpine Linux container. This should eliminate musl from the picture, and exercise the ENOEXEC fallback (from glibc), invoking /bin/sh (aka busybox) under argv[0] "foobar", and trigger the "unknown applet" bug in busybox. However, this does not happen. Instead, I get "hello" printed. How is that possible? The solution is that glibc *too* has a bug, and that bug hides the busybox bug. Namely, in glibc, going back to historical commit commit 6a032d81581978187f562e5533a32e0a6a3d352b (tag: cvs/libc-960210) Author: Roland McGrath <rol...@gnu.org> Date: Sat Feb 10 10:00:27 1996 +0000 Sat Feb 10 04:18:48 1996 Roland McGrath <rol...@churchy.gnu.ai.mit.edu> * posix/execvp.c: If execv fails with ENOEXEC, run the shell on the file. Fri Feb 9 11:46:45 1996 Roland McGrath <rol...@churchy.gnu.ai.mit.edu> * time/Makefile (CFLAGS-zdump.c, CFLAGS-zic.c, CFLAGS-ialloc.c, CFLAGS-scheck.c): Use -DNOID instead of -Wno-unused. * hurd/Makefile (user-interfaces): Added hurd/tioctl. (note the date: 1996!), the POSIX-mandated fallback execv(<shell path>, { argv[0], file, argv[1], ..., NULL }) is not being done. Instead, the following is done: execv(<shell path>, { <shell path>, file, argv[1], ..., NULL }) In other words, the original argv[0] is not preserved, but is replaced by <shell path>. (Look for _PATH_BSHELL in said historical glibc commit, and also in today's glibc file "posix/execvpe.c".) This can be demonstrated with: $ PATH=.:$PATH strace -etrace=execve test-execvp execve("./test-execvp", ["test-execvp"], 0x7ffc0d1e5248 /* 85 vars */) = 0 execve("./hello.sh", ["foobar"], 0x7ffc528e14a8 /* 85 vars */) = -1 ENOEXEC (Exec format error) execve("/bin/sh", ["/bin/sh", "./hello.sh"], 0x7ffc528e14a8 /* 85 vars */) = 0 hello +++ exited with 0 +++ The third execve() call should be: execve("/bin/sh", ["foobar", "./hello.sh"], 0x7ffc528e14a8 /* 85 vars */) = 0 ^^^^^^^^ Laszlo _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://listman.redhat.com/mailman/listinfo/libguestfs