From f94c11d978efae1761dc27be9ffe75f365323a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C5=8Dshin?= Date: Fri, 15 Dec 2023 12:23:58 -0500 Subject: [PATCH] Loader path security (#1012) The ape loader now passes the program executable name directly as a register. `x2` is used on aarch64, `%rdx` on x86_64. This is passed as the third argument to `cosmo()` (M1) or `Launch` (non-M1) and is assigned to the global `__program_executable_name`. `GetProgramExecutableName` now returns this global's value, setting it if it is initially null. `InitProgramExecutableName` first tries exotic, secure methods: `KERN_PROC_PATHNAME` on FreeBSD/NetBSD, and `/proc` on Linux. If those produce a reasonable response (i.e., not `"/usr/bin/ape"`, which happens with the loader before this change), that is used. Otherwise, if `issetugid()`, the empty string is used. Otherwise, the old argv/envp parsing code is run. The value returned from the loader is always the full absolute path of the binary to be executed, having passed through `realpath`. For the non-M1 loader, this necessitated writing `RealPath`, which uses `readlinkat` of `"/proc/self/fd/[progfd]"` on Linux, `F_GETPATH` on Xnu, and the `__realpath` syscall on OpenBSD. On FreeBSD/NetBSD, it punts to `GetProgramExecutableName`, which is secure on those OSes. With the loader, all platforms now have a secure program executable name. With no loader or an old loader, everything still works as it did, but setuid/setgid is not supported if the insecure pathfinding code would have been needed. Fixes #991. --- ape/ape-m1.c | 51 +++------ ape/launch.S | 13 ++- ape/loader.c | 88 +++++++++++---- examples/env.c | 4 +- libc/calls/getprogramexecutablename.greg.c | 100 ++++++++++-------- libc/crt/crt.S | 4 + libc/nexgen32e/program_executable_name.c | 21 ++++ libc/runtime/cosmo2.c | 3 +- libc/runtime/runtime.h | 1 + .../calls/getprogramexecutablename_test.c | 43 ++++---- 10 files changed, 192 insertions(+), 136 deletions(-) create mode 100644 libc/nexgen32e/program_executable_name.c diff --git a/ape/ape-m1.c b/ape/ape-m1.c index 677bf3545e5..0b5860e5cb7 100644 --- a/ape/ape-m1.c +++ b/ape/ape-m1.c @@ -36,8 +36,6 @@ #include #define pagesz 16384 -#define VARNAME "COSMOPOLITAN_PROGRAM_EXECUTABLE=" -#define VARSIZE (sizeof(VARNAME) - 1) /* maximum path size that cosmo can take */ #define PATHSIZE (PATH_MAX < 1024 ? PATH_MAX : 1024) #define SYSLIB_MAGIC ('s' | 'l' << 8 | 'i' << 16 | 'b' << 24) @@ -203,11 +201,8 @@ struct PathSearcher { unsigned long namelen; const char *name; const char *syspath; - char varname[VARSIZE]; char path[PATHSIZE]; }; -_Static_assert(offsetof(struct PathSearcher, varname) + VARSIZE == - offsetof(struct PathSearcher, path), "struct layout"); struct ApeLoader { struct PathSearcher ps; @@ -321,17 +316,21 @@ __attribute__((__noreturn__)) static void Pexit(const char *c, int failed, } static char AccessCommand(struct PathSearcher *ps, unsigned long pathlen) { - if (!pathlen && *ps->name != '/') { - if (!getcwd(ps->path, sizeof(ps->path) - 1 - ps->namelen)) { - Pexit("getcwd", -errno, "failed"); - } - pathlen = strlen(ps->path); - } else if (pathlen + 1 + ps->namelen + 1 > sizeof(ps->path)) { + char buf[PATH_MAX]; + size_t n; + if (pathlen + 1 + ps->namelen + 1 > sizeof(ps->path)) { return 0; } if (pathlen && ps->path[pathlen - 1] != '/') ps->path[pathlen++] = '/'; memmove(ps->path + pathlen, ps->name, ps->namelen); ps->path[pathlen + ps->namelen] = 0; + if (!realpath(ps->path, buf)) { + Pexit(ps->path, -errno, "realpath"); + } + if ((n = strlen(buf)) >= sizeof(ps->path)) { + Pexit(buf, 0, "too long"); + } + memcpy(ps->path, buf, n + 1); if (!access(ps->path, X_OK)) { if (ps->indirect) { ps->namelen -= 4; @@ -563,7 +562,8 @@ static long sys_pselect(int nfds, fd_set *readfds, fd_set *writefds, __attribute__((__noreturn__)) static void Spawn(const char *exe, int fd, long *sp, struct ElfEhdr *e, struct ElfPhdr *p, - struct Syslib *lib) { + struct Syslib *lib, + char *path) { long rc; int prot; int flags; @@ -734,10 +734,10 @@ __attribute__((__noreturn__)) static void Spawn(const char *exe, int fd, close(fd); register long *x0 __asm__("x0") = sp; + register char *x2 __asm__("x2") = path; register struct Syslib *x15 __asm__("x15") = lib; register long x16 __asm__("x16") = e->e_entry; __asm__ volatile("mov\tx1,#0\n\t" - "mov\tx2,#0\n\t" "mov\tx3,#0\n\t" "mov\tx4,#0\n\t" "mov\tx5,#0\n\t" @@ -767,7 +767,7 @@ __attribute__((__noreturn__)) static void Spawn(const char *exe, int fd, "mov\tx0,#0\n\t" "br\tx16" : /* no outputs */ - : "r"(x0), "r"(x15), "r"(x16) + : "r"(x0), "r"(x2), "r"(x15), "r"(x16) : "memory"); __builtin_unreachable(); } @@ -891,7 +891,7 @@ static const char *TryElf(struct ApeLoader *M, union ElfEhdrBuf *ebuf, auxv[28] = 0; /* we're now ready to load */ - Spawn(exe, fd, sp, e, p, &M->lib); + Spawn(exe, fd, sp, e, p, &M->lib, M->ps.path); } int main(int argc, char **argv, char **envp) { @@ -900,8 +900,7 @@ int main(int argc, char **argv, char **envp) { struct ApeLoader *M; long *sp, *sp2, *auxv; union ElfEhdrBuf *ebuf; - char *p, *pe, *exe, *prog, - *execfn, *shell, **varpos; + char *p, *pe, *exe, *prog, *execfn, *shell; /* allocate loader memory in program's arg block */ n = sizeof(struct ApeLoader); @@ -965,13 +964,9 @@ int main(int argc, char **argv, char **envp) { /* getenv("_") is close enough to at_execfn */ execfn = argc > 0 ? argv[0] : 0; - varpos = 0; for (i = 0; envp[i]; ++i) { if (envp[i][0] == '_' && envp[i][1] == '=') { execfn = envp[i] + 2; - } else if (!memcmp(VARNAME, envp[i], VARSIZE)) { - assert(!varpos); - varpos = envp + i; } } @@ -982,7 +977,7 @@ int main(int argc, char **argv, char **envp) { /* create new bottom of stack for spawned program system v abi aligns this on a 16-byte boundary grows down the alloc by poking the guard pages */ - n = (auxv - sp + !varpos + AUXV_WORDS + 1) * sizeof(long); + n = (auxv - sp + AUXV_WORDS + 1) * sizeof(long); sp2 = (long *)__builtin_alloca(n); if ((long)sp2 & 15) ++sp2; for (; n > 0; n -= pagesz) { @@ -991,12 +986,6 @@ int main(int argc, char **argv, char **envp) { memmove(sp2, sp, (auxv - sp) * sizeof(long)); argv = (char **)(sp2 + 1); envp = (char **)(sp2 + 1 + argc + 1); - if (varpos) { - varpos = (char **)((long *)varpos - sp + sp2); - } else { - varpos = envp + i++; - *(envp + i) = 0; - } auxv = (long *)(envp + i + 1); sp = sp2; @@ -1061,12 +1050,6 @@ int main(int argc, char **argv, char **envp) { } pe = ebuf->buf + rc; - /* inject program executable as first environment variable, - swapping the old first variable for it. */ - memmove(M->ps.varname, VARNAME, VARSIZE); - *varpos = *envp; - *envp = M->ps.varname; - /* generate some hard random data */ if ((rc = sys_getentropy(M->rando, sizeof(M->rando))) < 0) { Pexit(argv[0], rc, "getentropy"); diff --git a/ape/launch.S b/ape/launch.S index d4d89651a1c..87489f9f558 100644 --- a/ape/launch.S +++ b/ape/launch.S @@ -31,17 +31,16 @@ // // @param rdi is passed through as-is // @param rsi is address of entrypoint (becomes zero) -// @param rdx is stack pointer (becomes zero) -// @param rcx is passed through as-is +// @param rdx is passed through as-is +// @param rcx is stack pointer (becomes r8) // @noreturn Launch: #ifdef __aarch64__ mov x16,x1 - mov sp,x2 + mov sp,x3 mov x1,0 - mov x2,0 - mov x3,0 + mov x3,x4 mov x4,0 mov x5,0 mov x6,0 @@ -71,6 +70,8 @@ Launch: #else + mov %rcx,%rsp + mov %r8,%rcx xor %r8d,%r8d xor %r9d,%r9d xor %r10d,%r10d @@ -79,8 +80,6 @@ Launch: xor %r13d,%r13d xor %r14d,%r14d xor %r15d,%r15d - mov %rdx,%rsp - xor %edx,%edx push %rsi xor %esi,%esi xor %ebp,%ebp diff --git a/ape/loader.c b/ape/loader.c index b65bd147f65..87369cc4243 100644 --- a/ape/loader.c +++ b/ape/loader.c @@ -87,6 +87,8 @@ #define MIN(X, Y) ((Y) > (X) ? (X) : (Y)) #define MAX(X, Y) ((Y) < (X) ? (X) : (Y)) +#define PATH_MAX 1024 /* XXX verify */ + #define SupportsLinux() (SUPPORT_VECTOR & LINUX) #define SupportsXnu() (SUPPORT_VECTOR & XNU) #define SupportsFreebsd() (SUPPORT_VECTOR & FREEBSD) @@ -212,17 +214,18 @@ struct PathSearcher { const char *name; const char *syspath; unsigned long namelen; - char path[1024]; + char path[PATH_MAX]; }; struct ApeLoader { union ElfPhdrBuf phdr; struct PathSearcher ps; - char path[1024]; + char path[PATH_MAX]; }; EXTERN_C long SystemCall(long, long, long, long, long, long, long, int); -EXTERN_C void Launch(void *, long, void *, int) __attribute__((__noreturn__)); +EXTERN_C void +Launch(void *, long, void *, void *, int) __attribute__((__noreturn__)); extern char __executable_start[]; extern char _end[]; @@ -239,12 +242,13 @@ static int StrCmp(const char *l, const char *r) { return (l[i] & 255) - (r[i] & 255); } -static const char *BaseName(const char *s) { - int c; - const char *b = ""; +#if 0 + +static const char *StrRChr(const char *s, int c) { + const char *b = 0; if (s) { - while ((c = *s++)) { - if (c == '/') { + for (; *s; ++s) { + if (*s == c) { b = s; } } @@ -252,6 +256,13 @@ static const char *BaseName(const char *s) { return b; } +static const char *BaseName(const char *s) { + const char *b = StrRChr(s, '/'); + return b ? b + 1 : s; +} + +#endif + static void Bzero(void *a, unsigned long n) { long z; char *p, *e; @@ -343,7 +354,7 @@ static char *Utox(char p[19], unsigned long x) { return p; } -static char *Utoa(char p[21], unsigned long x) { +static char *Utoa(char p[20], unsigned long x) { char t; unsigned long i, a, b; i = 0; @@ -534,6 +545,40 @@ __attribute__((__noreturn__)) static void Pexit(int os, const char *c, int rc, Exit(127, os); } +#define PSFD "/proc/self/fd/" + +static int RealPath(int os, int fd, char *path, char **resolved) { + char buf[PATH_MAX]; + int rc; + if (IsLinux()) { + char psfd[sizeof(PSFD) + 19]; + MemMove(psfd, PSFD, sizeof(PSFD) - 1); + Utoa(psfd + sizeof(PSFD) - 1, fd); + rc = SystemCall(-100, (long)psfd, (long)buf, PATH_MAX, 0, 0, 0, + IsAarch64() ? 78 : 267); + if (rc >= 0) { + if (rc == PATH_MAX) { + rc = -36; + } else { + buf[rc] = 0; + } + } + } else if (IsXnu()) { + rc = SystemCall(fd, 50, (long)buf, 0, 0, 0, 0, 92 | 0x2000000); + } else if (IsOpenbsd()) { + rc = SystemCall((long)path, (long)buf, 0, 0, 0, 0, 0, 115); + } else { + *resolved = 0; + return 0; + } + if (rc >= 0) { + MemMove(path, buf, StrLen(buf) + 1); + *resolved = path; + rc = 0; + } + return rc; +} + static char AccessCommand(struct PathSearcher *ps, unsigned long pathlen) { if (pathlen + 1 + ps->namelen + 1 > sizeof(ps->path)) return 0; if (pathlen && ps->path[pathlen - 1] != '/') ps->path[pathlen++] = '/'; @@ -599,8 +644,9 @@ static char *Commandv(struct PathSearcher *ps, int os, const char *name, } } -__attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd, - long *sp, unsigned long pagesz, +__attribute__((__noreturn__)) static void Spawn(int os, const char *exe, + char *path, int fd, long *sp, + unsigned long pagesz, struct ElfEhdr *e, struct ElfPhdr *p) { long rc; @@ -757,12 +803,12 @@ __attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd, Msyscall(dynbase + code, codesize, os); /* call program entrypoint */ - Launch(IsFreebsd() ? sp : 0, dynbase + e->e_entry, sp, os); + Launch(IsFreebsd() ? sp : 0, dynbase + e->e_entry, path, sp, os); } static const char *TryElf(struct ApeLoader *M, union ElfEhdrBuf *ebuf, - const char *exe, int fd, long *sp, long *auxv, - unsigned long pagesz, int os) { + const char *exe, char *path, int fd, long *sp, + long *auxv, unsigned long pagesz, int os) { long i, rc; unsigned size; struct ElfEhdr *e; @@ -877,7 +923,7 @@ static const char *TryElf(struct ApeLoader *M, union ElfEhdrBuf *ebuf, } /* we're now ready to load */ - Spawn(os, exe, fd, sp, pagesz, e, p); + Spawn(os, exe, path, fd, sp, pagesz, e, p); } __attribute__((__noreturn__)) static void ShowUsage(int os, int fd, int rc) { @@ -1035,6 +1081,8 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, Pexit(os, prog, 0, "not found (maybe chmod +x or ./ needed)"); } else if ((fd = Open(exe, O_RDONLY, 0, os)) < 0) { Pexit(os, exe, fd, "open"); + } else if ((rc = RealPath(os, fd, exe, &prog)) < 0) { + Pexit(os, exe, rc, "realpath"); } else if ((rc = Pread(fd, ebuf->buf, sizeof(ebuf->buf), 0, os)) < 0) { Pexit(os, exe, rc, "read"); } else if ((unsigned long)rc < sizeof(ebuf->ehdr)) { @@ -1042,12 +1090,6 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, } pe = ebuf->buf + rc; - /* change argv[0] to resolved path if it's ambiguous */ - if (argc > 0 && ((*prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) || - !StrCmp(BaseName(prog), argv[0]))) { - argv[0] = exe; - } - /* ape intended behavior 1. if ape, will scan shell script for elf printf statements 2. shell script may have multiple lines producing elf headers @@ -1080,9 +1122,9 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, } } if (i >= sizeof(ebuf->ehdr)) { - TryElf(M, ebuf, exe, fd, sp, auxv, pagesz, os); + TryElf(M, ebuf, exe, prog, fd, sp, auxv, pagesz, os); } } } - Pexit(os, exe, 0, TryElf(M, ebuf, exe, fd, sp, auxv, pagesz, os)); + Pexit(os, exe, 0, TryElf(M, ebuf, exe, prog, fd, sp, auxv, pagesz, os)); } diff --git a/examples/env.c b/examples/env.c index 7fc97166370..f3395ab36cb 100644 --- a/examples/env.c +++ b/examples/env.c @@ -2,9 +2,9 @@ #include "libc/runtime/runtime.h" int main(int argc, char* argv[]) { - printf("%s\n", argv[0]); + fprintf(stderr, "%s (%s)\n", argv[0], GetProgramExecutableName()); for (char **p = environ; *p; ++p) { - printf(" %s\n", *p); + printf("%s\n", *p); } return 0; } diff --git a/libc/calls/getprogramexecutablename.greg.c b/libc/calls/getprogramexecutablename.greg.c index 247f2fae65a..05c690b98d0 100644 --- a/libc/calls/getprogramexecutablename.greg.c +++ b/libc/calls/getprogramexecutablename.greg.c @@ -51,7 +51,14 @@ static inline int IsAlpha(int c) { } static inline void InitProgramExecutableNameImpl(void) { + size_t n; + ssize_t got; + char c, *q, *b; + if (__program_executable_name) { + /* already set by the loader */ + return; + } if (IsWindows()) { int n = GetModuleFileName(0, g_prog.u.buf16, ARRAYLEN(g_prog.u.buf16)); for (int i = 0; i < n; ++i) { @@ -69,21 +76,44 @@ static inline void InitProgramExecutableNameImpl(void) { g_prog.u.buf16[2] = '/'; } tprecode16to8(g_prog.u.buf, sizeof(g_prog.u.buf), g_prog.u.buf16); + goto UseBuf; + } + if (IsMetal()) { + __program_executable_name = APE_COM_NAME; return; } - char c, *q; - if (IsMetal()) { - q = APE_COM_NAME; - goto CopyString; + b = g_prog.u.buf; + n = sizeof(g_prog.u.buf) - 1; + if (IsFreebsd() || IsNetbsd()) { + int cmd[4]; + cmd[0] = CTL_KERN; + cmd[1] = KERN_PROC; + if (IsFreebsd()) { + cmd[2] = KERN_PROC_PATHNAME_FREEBSD; + } else { + cmd[2] = KERN_PROC_PATHNAME_NETBSD; + } + cmd[3] = -1; // current process + if (sys_sysctl(cmd, ARRAYLEN(cmd), b, &n, 0, 0) != -1) { + if (strcmp(b, "/usr/bin/ape")) { // XX old loader; warn? + goto UseBuf; + } + } + } + if (IsLinux()) { + if ((got = sys_readlinkat(AT_FDCWD, "/proc/self/exe", b, n)) > 0 || + (got = sys_readlinkat(AT_FDCWD, "/proc/curproc/file", b, n)) > 0) { + b[got] = 0; + if (strcmp(b, "/usr/bin/ape")) { + goto UseBuf; + } + } } - /* the new-style loader supplies the full program path as the first - environment variable. in the spirit of Postel's Law ("be liberal - in what you accept"), we use __getenv to read it. */ - if ((q = __getenv(__envp, "COSMOPOLITAN_PROGRAM_EXECUTABLE").s)) { - strlcpy(g_prog.u.buf, q, sizeof(g_prog.u.buf)); - return; + if (issetugid()) { + /* give up prior to using less secure methods */ + goto UseEmpty; } // if argv[0] exists then turn it into an absolute path. we also try @@ -107,41 +137,17 @@ static inline void InitProgramExecutableNameImpl(void) { } } *p = 0; - if (!sys_faccessat(AT_FDCWD, g_prog.u.buf, F_OK, 0)) return; + if (!sys_faccessat(AT_FDCWD, g_prog.u.buf, F_OK, 0)) goto UseBuf; p = WRITE32LE(p, READ32LE(".com")); *p = 0; - if (!sys_faccessat(AT_FDCWD, g_prog.u.buf, F_OK, 0)) return; - } - - // if getenv("_") exists then use that - for (char **ep = __envp; (q = *ep); ++ep) { - if (*q++ == '_' && *q++ == '=') { - goto CopyString; - } + if (!sys_faccessat(AT_FDCWD, g_prog.u.buf, F_OK, 0)) goto UseBuf; } - // if argv[0] doesn't exist, then fallback to interpreter name - ssize_t got; - char *b = g_prog.u.buf; - size_t n = sizeof(g_prog.u.buf) - 1; - if ((got = sys_readlinkat(AT_FDCWD, "/proc/self/exe", b, n)) > 0 || - (got = sys_readlinkat(AT_FDCWD, "/proc/curproc/file", b, n)) > 0) { - b[got] = 0; - return; - } - if (IsFreebsd() || IsNetbsd()) { - int cmd[4]; - cmd[0] = CTL_KERN; - cmd[1] = KERN_PROC; - if (IsFreebsd()) { - cmd[2] = KERN_PROC_PATHNAME_FREEBSD; - } else { - cmd[2] = KERN_PROC_PATHNAME_NETBSD; - } - cmd[3] = -1; // current process - if (sys_sysctl(cmd, ARRAYLEN(cmd), b, &n, 0, 0) != -1) { - return; - } + /* the previous loader supplied the full program path as the first + environment variable. we also try "_". */ + if ((q = __getenv(__envp, "COSMOPOLITAN_PROGRAM_EXECUTABLE").s) || + (q = __getenv(__envp, "_").s)) { + goto CopyString; } // give up and just copy argv[0] into it @@ -155,14 +161,18 @@ static inline void InitProgramExecutableNameImpl(void) { } } *p = 0; - return; + goto UseBuf; } // if we don't even have that then empty the string +UseEmpty: g_prog.u.buf[0] = 0; + +UseBuf: + __program_executable_name = g_prog.u.buf; } -void __InitProgramExecutableName(void) { +static void InitProgramExecutableName(void) { int e = errno; InitProgramExecutableNameImpl(); errno = e; @@ -172,6 +182,6 @@ void __InitProgramExecutableName(void) { * Returns absolute path of program. */ char *GetProgramExecutableName(void) { - cosmo_once(&g_prog.once, __InitProgramExecutableName); - return g_prog.u.buf; + cosmo_once(&g_prog.once, InitProgramExecutableName); + return __program_executable_name; } diff --git a/libc/crt/crt.S b/libc/crt/crt.S index b37ded5e116..07978f47b31 100644 --- a/libc/crt/crt.S +++ b/libc/crt/crt.S @@ -62,6 +62,8 @@ _start: // set operating system when already detected 1: mov %cl,__hostos(%rip) + mov %rdx,__program_executable_name(%rip) + // get startup timestamp as early as possible // its used by --strace flag and kprintf() %T rdtsc @@ -140,6 +142,8 @@ _start: // should be set to zero on other platforms mov x1,x15 +// third arg (x2) is the program path passed by ape-m1.c + // switch to c code bl cosmo .unreachable diff --git a/libc/nexgen32e/program_executable_name.c b/libc/nexgen32e/program_executable_name.c new file mode 100644 index 00000000000..bbc9a1183eb --- /dev/null +++ b/libc/nexgen32e/program_executable_name.c @@ -0,0 +1,21 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/runtime/runtime.h" + +char *__program_executable_name; diff --git a/libc/runtime/cosmo2.c b/libc/runtime/cosmo2.c index 5832dfcd685..57067b5abd8 100644 --- a/libc/runtime/cosmo2.c +++ b/libc/runtime/cosmo2.c @@ -78,7 +78,7 @@ static const char *DecodeMagnum(const char *p, long *r) { return *r = x, p; } -wontreturn textstartup void cosmo(long *sp, struct Syslib *m1) { +wontreturn textstartup void cosmo(long *sp, struct Syslib *m1, char *exename) { // get startup timestamp as early as possible // its used by --strace and also kprintf() %T @@ -108,6 +108,7 @@ wontreturn textstartup void cosmo(long *sp, struct Syslib *m1) { __envp = envp; __auxv = auxv; environ = envp; + __program_executable_name = exename; program_invocation_name = argv[0]; __oldstack = (intptr_t)sp; diff --git a/libc/runtime/runtime.h b/libc/runtime/runtime.h index 364f4d26da3..5e5958f5a6d 100644 --- a/libc/runtime/runtime.h +++ b/libc/runtime/runtime.h @@ -71,6 +71,7 @@ extern char **__argv; extern char **__envp; extern unsigned long *__auxv; extern intptr_t __oldstack; +extern char *__program_executable_name; extern uint64_t __nosync; extern int __strace; extern int __ftrace; diff --git a/test/libc/calls/getprogramexecutablename_test.c b/test/libc/calls/getprogramexecutablename_test.c index 38945bd66d8..8e480f960bb 100644 --- a/test/libc/calls/getprogramexecutablename_test.c +++ b/test/libc/calls/getprogramexecutablename_test.c @@ -28,25 +28,26 @@ #include "libc/testlib/testlib.h" static char *self; -static bool skipcosmotests; - -void SetUp(void) { - self = GetProgramExecutableName(); -} void SetUpOnce(void) { - if (!getenv("COSMOPOLITAN_PROGRAM_EXECUTABLE")) { - fprintf(stderr, - "warning: old ape loader detected; skipping some tests %m\n"); - skipcosmotests = true; - } + self = GetProgramExecutableName(); testlib_enable_tmp_setup_teardown(); } __attribute__((__constructor__)) static void Child(int argc, char *argv[]) { + static bool skiparg0tests; + if (!__program_executable_name && !IsFreebsd() && !IsNetbsd()) { + skiparg0tests = true; + if (argc < 2) { + fprintf(stderr, "warning: old/no loader; skipping argv[0] tests\n"); + } + } if (argc >= 2 && !strcmp(argv[1], "Child")) { - ASSERT_EQ(3, argc); + ASSERT_EQ(argc, 4); EXPECT_STREQ(argv[2], GetProgramExecutableName()); + if (!skiparg0tests) { + EXPECT_STREQ(argv[3], argv[0]); + } exit(g_testlib_failed); } } @@ -59,33 +60,33 @@ TEST(GetProgramExecutableName, ofThisFile) { TEST(GetProgramExecutableName, nullEnv) { SPAWN(fork); - execve(self, (char *[]){self, "Child", self, 0}, (char *[]){0}); + execve(self, (char *[]){self, "Child", self, self, 0}, (char *[]){0}); abort(); EXITS(0); } TEST(GetProramExecutableName, weirdArgv0NullEnv) { SPAWN(fork); - execve(self, (char *[]){"hello", "Child", self, 0}, (char *[]){0}); + execve(self, (char *[]){"hello", "Child", self, "hello", 0}, (char *[]){0}); abort(); EXITS(0); } TEST(GetProgramExecutableName, weirdArgv0CosmoVar) { - if (skipcosmotests) return; char buf[32 + PATH_MAX]; stpcpy(stpcpy(buf, "COSMOPOLITAN_PROGRAM_EXECUTABLE="), self); SPAWN(fork); - execve(self, (char *[]){"hello", "Child", self, 0}, (char *[]){buf, 0}); + execve(self, (char *[]){"hello", "Child", self, "hello", 0}, + (char *[]){buf, 0}); abort(); EXITS(0); } TEST(GetProgramExecutableName, weirdArgv0WrongCosmoVar) { - if (skipcosmotests) return; char *bad = "COSMOPOLITAN_PROGRAM_EXECUTABLE=hi"; SPAWN(fork); - execve(self, (char *[]){"hello", "Child", self, 0}, (char *[]){bad, 0}); + execve(self, (char *[]){"hello", "Child", self, "hello", 0}, + (char *[]){bad, 0}); abort(); EXITS(0); } @@ -104,13 +105,7 @@ TEST(GetProgramExecutableName, movedSelf) { ASSERT_NE(NULL, getcwd(buf, BUFSIZ - 5)); stpcpy(buf + strlen(buf), "/test"); SPAWN(fork); - execve(buf, (char *[]){"hello", "Child", buf, 0}, (char *[]){0}); + execve(buf, (char *[]){"hello", "Child", buf, "hello", 0}, (char *[]){0}); abort(); EXITS(0); } - -void __InitProgramExecutableName(void); - -BENCH(GetProgramExecutableName, bench) { - EZBENCH2("Init", donothing, __InitProgramExecutableName()); -}