Bug 15481

Summary: /bin/sh does not work when invoked with argv[0] different from "sh"
Product: Busybox Reporter: Laszlo Ersek <laszlo.ersek>
Component: Standard ComplianceAssignee: unassigned
Status: NEW ---    
Severity: normal CC: busybox-cvs
Priority: P5    
Version: 1.35.x   
Target Milestone: ---   
Hardware: All   
OS: Linux   
Host: Target:
Build:

Description Laszlo Ersek 2023-03-22 16:01:03 UTC
The POSIX specification (Issue 7, TC2 applied) explains at

  https://pubs.opengroup.org/onlinepubs/9699919799/functions/execvp.html

that, 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.

On a system where "/bin/sh" is provided by busybox (that is, "/bin/sh" is a symlink to "/bin/busybox"), any execvp() implementation that uses "/bin/sh" for <shell path> will break the required ENOEXEC behavior, in the general case. That's because busybox will not recognize from argv[0] (= file) that it's supposed to behave as the shell.

The simplest way to demonstrate the bug is this:

bash$ ( exec -a foobar /bin/sh <<< "echo hello" )
foobar: applet not found

(Unfortunately, the bug cannot be demonstrated via widely used C libraries, such as glibc and musl. The reason is that *both* glibc and musl have bugs that *hide* the busybox bug.

- In musl, the ENOEXEC path is not handled at all, so musl doesn't even try to invoke the shell.

- In glibc, the POSIX-mandated argument list for the shell is not implemented properly. Again, the argument list is supposed to be

  execv(<shell path>, { argv[0], file, argv[1], ..., NULL })

but glibc does

  execv(<shell path>, { <shell path>, file, argv[1], ..., NULL })

which is precisely what masks this busybox bug.

Refer to _PATH_BSHELL in historical glibc commit 6a032d815819, and in today's glibc file "posix/execvpe.c".)

The ask here is to provide a standalone /bin/sh binary that is not sensitive to what argv[0] it is invoked under. Thanks.
Comment 1 Eric Blake 2023-03-22 20:03:45 UTC
In parallel, I've opened https://www.austingroupbugs.net/view.php?id=1645; maybe POSIX will be relaxed to allow libc implementations to fall back to execl("/bin/sh", "sh", "-c", ". quoted_file", argv[0], argv[1], ..., NULL) or execl("/bin/sh", "sh", "-c", "exec -a \"$0\" quoted_file \"$@\"", argv[0], argv[1], ..., NULL), at which point busybox can still depend on argv[0] ending in "sh" while passing the correct $0 to the shell script.  Let's see what the libc and POSIX folks say...
Comment 2 Eric Blake 2023-05-12 15:14:06 UTC
Compliance-wise, it looks like POSIX intends to relax things, so that as long as libc invokes the sh fallback with argv[0] of "sh" (or maybe "/bin/sh"), that is acceptable.  Thus, it is no longer as urgent that busybox consider installing a shim that performs shell functionality regardless of argv[0] contents.
https://austingroupbugs.net/view.php?id=1645#c6281