[Libguestfs] [libnbd PATCH v4 0/2] lib/utils: introduce async-signal-safe execvpe()

Eric Blake eblake at redhat.com
Wed Mar 22 16:24:35 UTC 2023


On Wed, Mar 22, 2023 at 04:53:42PM +0100, Laszlo Ersek wrote:
...
> > 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.

No joke.

Locally, I wrote:

$ cat foo
echo "hello from $0:$@"
$ chmod +x foo
$ bash ./foo foobar arg1
hello from ./foo:foobar arg1
$ bash -c ./foo foobar arg1
hello from ./foo:
$ bash -c 'exec -a "$0" ./foo "$@"' foobar arg1
hello from foobar:arg1
$ bash -c '. ./foo' foobar arg1
hello from foobar:arg1


The latter two match the behavior we want to see.  Alas, POSIX has not
yet standardized 'exec -a', but the ability to use 'sh -c' to set
argv0, plus . to source a file as shell commands, gets us to the same
place.

> 
> 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 <roland at gnu.org>
> Date:   Sat Feb 10 10:00:27 1996 +0000
> 
>     Sat Feb 10 04:18:48 1996  Roland McGrath  <roland at 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  <roland at 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".)

Wow.  This is so historically old that it's probably worth asking on
the Austin Group if argv[0] must be preserved or can be changed to the
shell name, and/or whether the standard should add support for 'exec
-a argv0 cmd args' as a way to set argv[0] despite this long-standing
glibc behavior.  I'll reply back when I have some URLs to list
archives of my pending questions.

> 
> 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
> 

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org


More information about the Libguestfs mailing list