Support binfmt_misc P flag in APE loader (#1058)

This allows ape to automatically preserve `argv[0]` [as of Linux kernel
5.12][0] if the [binfmt_misc][1] registration contains the P flag.

This also removes may_path_search, which was identical to the literally
flag in usage. As a result, FindCommand is subsumed into Commandv.

[0]: https://patchew.org/QEMU/20210222105004.1642234-1-laurent@vivier.eu/
[1]: https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html
This commit is contained in:
Jōshin 2024-01-05 15:35:01 -05:00 committed by GitHub
parent 44a463e4d2
commit 412a200ae4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -141,6 +141,9 @@
#define AT_PHENT 4 #define AT_PHENT 4
#define AT_PHNUM 5 #define AT_PHNUM 5
#define AT_PAGESZ 6 #define AT_PAGESZ 6
#define AT_FLAGS 8
#define AT_FLAGS_PRESERVE_ARGV0_BIT 0
#define AT_FLAGS_PRESERVE_ARGV0 (1 << AT_FLAGS_PRESERVE_ARGV0_BIT)
#define AT_EXECFN_LINUX 31 #define AT_EXECFN_LINUX 31
#define AT_EXECFN_NETBSD 2014 #define AT_EXECFN_NETBSD 2014
#define X_OK 1 #define X_OK 1
@ -572,39 +575,15 @@ static char SearchPath(struct PathSearcher *ps) {
} }
} }
static char FindCommand(struct PathSearcher *ps) {
ps->path[0] = 0;
/* paths are always 100% taken literally when a slash exists
$ ape foo/bar.com arg1 arg2 */
if (MemChr(ps->name, '/', ps->namelen)) {
return AccessCommand(ps, 0);
}
/* we don't run files in the current directory
$ ape foo.com arg1 arg2
unless $PATH has an empty string entry, e.g.
$ expert PATH=":/bin"
$ ape foo.com arg1 arg2
however we will execute this
$ ape - foo.com foo.com arg1 arg2
because cosmo's execve needs it */
if (ps->literally && AccessCommand(ps, 0)) {
return 1;
}
/* otherwise search for name on $PATH */
return SearchPath(ps);
}
static char *Commandv(struct PathSearcher *ps, int os, char *name, static char *Commandv(struct PathSearcher *ps, int os, char *name,
const char *syspath, int may_path_search) { const char *syspath) {
if (!may_path_search) return name; if (!(ps->namelen = StrLen((ps->name = name)))) return 0;
if (ps->literally || MemChr(ps->name, '/', ps->namelen)) return name;
ps->os = os; ps->os = os;
ps->syspath = syspath ? syspath : "/bin:/usr/local/bin:/usr/bin"; ps->syspath = syspath ? syspath : "/bin:/usr/local/bin:/usr/bin";
if (!(ps->namelen = StrLen((ps->name = name)))) return 0;
if (ps->namelen + 1 > sizeof(ps->path)) return 0; if (ps->namelen + 1 > sizeof(ps->path)) return 0;
if (FindCommand(ps)) { ps->path[0] = 0;
if (SearchPath(ps)) {
return ps->path; return ps->path;
} else { } else {
return 0; return 0;
@ -913,10 +892,10 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp,
char dl) { char dl) {
int rc, n; int rc, n;
unsigned i; unsigned i;
char literally;
const char *ape; const char *ape;
int c, fd, os, argc; int c, fd, os, argc;
struct ApeLoader *M; struct ApeLoader *M;
char arg0, literally;
unsigned long pagesz; unsigned long pagesz;
union ElfEhdrBuf *ebuf; union ElfEhdrBuf *ebuf;
long *auxv, *ap, *endp, *sp2; long *auxv, *ap, *endp, *sp2;
@ -961,11 +940,14 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp,
/* detect netbsd and find end of words */ /* detect netbsd and find end of words */
pagesz = 0; pagesz = 0;
arg0 = 0;
for (ap = auxv; ap[0]; ap += 2) { for (ap = auxv; ap[0]; ap += 2) {
if (ap[0] == AT_PAGESZ) { if (ap[0] == AT_PAGESZ) {
pagesz = ap[1]; pagesz = ap[1];
} else if (SupportsNetbsd() && !os && ap[0] == AT_EXECFN_NETBSD) { } else if (SupportsNetbsd() && !os && ap[0] == AT_EXECFN_NETBSD) {
os = NETBSD; os = NETBSD;
} else if (SupportsLinux() && ap[0] == AT_FLAGS) {
arg0 = !!(ap[1] & AT_FLAGS_PRESERVE_ARGV0);
} }
} }
if (!pagesz) { if (!pagesz) {
@ -979,8 +961,12 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp,
} }
/* we can load via shell, shebang, or binfmt_misc */ /* we can load via shell, shebang, or binfmt_misc */
int may_path_search = 1; if (arg0) {
if ((literally = argc >= 3 && !StrCmp(argv[1], "-"))) { literally = 1;
prog = (char *)sp[2];
argc = sp[2] = sp[0] - 2;
argv = (char **)((sp += 2) + 1);
} else if ((literally = argc >= 3 && !StrCmp(argv[1], "-"))) {
/* if the first argument is a hyphen then we give the user the /* if the first argument is a hyphen then we give the user the
power to change argv[0] or omit it entirely. most operating power to change argv[0] or omit it entirely. most operating
systems don't permit the omission of argv[0] but we do, b/c systems don't permit the omission of argv[0] but we do, b/c
@ -988,7 +974,6 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp,
prog = (char *)sp[3]; prog = (char *)sp[3];
argc = sp[3] = sp[0] - 3; argc = sp[3] = sp[0] - 3;
argv = (char **)((sp += 3) + 1); argv = (char **)((sp += 3) + 1);
may_path_search = 0;
} else if (argc < 2) { } else if (argc < 2) {
ShowUsage(os, 2, 1); ShowUsage(os, 2, 1);
} else { } else {
@ -1029,8 +1014,7 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp,
} }
/* resolve path of executable and read its first page */ /* resolve path of executable and read its first page */
if (!(exe = Commandv(&M->ps, os, prog, GetEnv(envp, "PATH"), if (!(exe = Commandv(&M->ps, os, prog, GetEnv(envp, "PATH")))) {
may_path_search))) {
Pexit(os, prog, 0, "not found (maybe chmod +x or ./ needed)"); Pexit(os, prog, 0, "not found (maybe chmod +x or ./ needed)");
} else if ((fd = Open(exe, O_RDONLY, 0, os)) < 0) { } else if ((fd = Open(exe, O_RDONLY, 0, os)) < 0) {
Pexit(os, exe, fd, "open"); Pexit(os, exe, fd, "open");