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.
This commit is contained in:
Jōshin 2023-12-15 12:23:58 -05:00 committed by GitHub
parent 8a10ccf9c4
commit f94c11d978
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 193 additions and 137 deletions

View file

@ -36,8 +36,6 @@
#include <unistd.h>
#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");

View file

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

View file

@ -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));
}

View file

@ -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;
}

View file

@ -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);
return;
goto UseBuf;
}
char c, *q;
if (IsMetal()) {
q = APE_COM_NAME;
goto CopyString;
__program_executable_name = APE_COM_NAME;
return;
}
/* 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;
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;
}
}
}
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 (!sys_faccessat(AT_FDCWD, g_prog.u.buf, F_OK, 0)) goto UseBuf;
}
// if getenv("_") exists then use that
for (char **ep = __envp; (q = *ep); ++ep) {
if (*q++ == '_' && *q++ == '=') {
goto CopyString;
}
}
// 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;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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());
}