mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-12 09:17:53 +00:00
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:
parent
8a10ccf9c4
commit
f94c11d978
10 changed files with 193 additions and 137 deletions
51
ape/ape-m1.c
51
ape/ape-m1.c
|
@ -36,8 +36,6 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define pagesz 16384
|
#define pagesz 16384
|
||||||
#define VARNAME "COSMOPOLITAN_PROGRAM_EXECUTABLE="
|
|
||||||
#define VARSIZE (sizeof(VARNAME) - 1)
|
|
||||||
/* maximum path size that cosmo can take */
|
/* maximum path size that cosmo can take */
|
||||||
#define PATHSIZE (PATH_MAX < 1024 ? PATH_MAX : 1024)
|
#define PATHSIZE (PATH_MAX < 1024 ? PATH_MAX : 1024)
|
||||||
#define SYSLIB_MAGIC ('s' | 'l' << 8 | 'i' << 16 | 'b' << 24)
|
#define SYSLIB_MAGIC ('s' | 'l' << 8 | 'i' << 16 | 'b' << 24)
|
||||||
|
@ -203,11 +201,8 @@ struct PathSearcher {
|
||||||
unsigned long namelen;
|
unsigned long namelen;
|
||||||
const char *name;
|
const char *name;
|
||||||
const char *syspath;
|
const char *syspath;
|
||||||
char varname[VARSIZE];
|
|
||||||
char path[PATHSIZE];
|
char path[PATHSIZE];
|
||||||
};
|
};
|
||||||
_Static_assert(offsetof(struct PathSearcher, varname) + VARSIZE ==
|
|
||||||
offsetof(struct PathSearcher, path), "struct layout");
|
|
||||||
|
|
||||||
struct ApeLoader {
|
struct ApeLoader {
|
||||||
struct PathSearcher ps;
|
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) {
|
static char AccessCommand(struct PathSearcher *ps, unsigned long pathlen) {
|
||||||
if (!pathlen && *ps->name != '/') {
|
char buf[PATH_MAX];
|
||||||
if (!getcwd(ps->path, sizeof(ps->path) - 1 - ps->namelen)) {
|
size_t n;
|
||||||
Pexit("getcwd", -errno, "failed");
|
if (pathlen + 1 + ps->namelen + 1 > sizeof(ps->path)) {
|
||||||
}
|
|
||||||
pathlen = strlen(ps->path);
|
|
||||||
} else if (pathlen + 1 + ps->namelen + 1 > sizeof(ps->path)) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (pathlen && ps->path[pathlen - 1] != '/') ps->path[pathlen++] = '/';
|
if (pathlen && ps->path[pathlen - 1] != '/') ps->path[pathlen++] = '/';
|
||||||
memmove(ps->path + pathlen, ps->name, ps->namelen);
|
memmove(ps->path + pathlen, ps->name, ps->namelen);
|
||||||
ps->path[pathlen + ps->namelen] = 0;
|
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 (!access(ps->path, X_OK)) {
|
||||||
if (ps->indirect) {
|
if (ps->indirect) {
|
||||||
ps->namelen -= 4;
|
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,
|
__attribute__((__noreturn__)) static void Spawn(const char *exe, int fd,
|
||||||
long *sp, struct ElfEhdr *e,
|
long *sp, struct ElfEhdr *e,
|
||||||
struct ElfPhdr *p,
|
struct ElfPhdr *p,
|
||||||
struct Syslib *lib) {
|
struct Syslib *lib,
|
||||||
|
char *path) {
|
||||||
long rc;
|
long rc;
|
||||||
int prot;
|
int prot;
|
||||||
int flags;
|
int flags;
|
||||||
|
@ -734,10 +734,10 @@ __attribute__((__noreturn__)) static void Spawn(const char *exe, int fd,
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
||||||
register long *x0 __asm__("x0") = sp;
|
register long *x0 __asm__("x0") = sp;
|
||||||
|
register char *x2 __asm__("x2") = path;
|
||||||
register struct Syslib *x15 __asm__("x15") = lib;
|
register struct Syslib *x15 __asm__("x15") = lib;
|
||||||
register long x16 __asm__("x16") = e->e_entry;
|
register long x16 __asm__("x16") = e->e_entry;
|
||||||
__asm__ volatile("mov\tx1,#0\n\t"
|
__asm__ volatile("mov\tx1,#0\n\t"
|
||||||
"mov\tx2,#0\n\t"
|
|
||||||
"mov\tx3,#0\n\t"
|
"mov\tx3,#0\n\t"
|
||||||
"mov\tx4,#0\n\t"
|
"mov\tx4,#0\n\t"
|
||||||
"mov\tx5,#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"
|
"mov\tx0,#0\n\t"
|
||||||
"br\tx16"
|
"br\tx16"
|
||||||
: /* no outputs */
|
: /* no outputs */
|
||||||
: "r"(x0), "r"(x15), "r"(x16)
|
: "r"(x0), "r"(x2), "r"(x15), "r"(x16)
|
||||||
: "memory");
|
: "memory");
|
||||||
__builtin_unreachable();
|
__builtin_unreachable();
|
||||||
}
|
}
|
||||||
|
@ -891,7 +891,7 @@ static const char *TryElf(struct ApeLoader *M, union ElfEhdrBuf *ebuf,
|
||||||
auxv[28] = 0;
|
auxv[28] = 0;
|
||||||
|
|
||||||
/* we're now ready to load */
|
/* 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) {
|
int main(int argc, char **argv, char **envp) {
|
||||||
|
@ -900,8 +900,7 @@ int main(int argc, char **argv, char **envp) {
|
||||||
struct ApeLoader *M;
|
struct ApeLoader *M;
|
||||||
long *sp, *sp2, *auxv;
|
long *sp, *sp2, *auxv;
|
||||||
union ElfEhdrBuf *ebuf;
|
union ElfEhdrBuf *ebuf;
|
||||||
char *p, *pe, *exe, *prog,
|
char *p, *pe, *exe, *prog, *execfn, *shell;
|
||||||
*execfn, *shell, **varpos;
|
|
||||||
|
|
||||||
/* allocate loader memory in program's arg block */
|
/* allocate loader memory in program's arg block */
|
||||||
n = sizeof(struct ApeLoader);
|
n = sizeof(struct ApeLoader);
|
||||||
|
@ -965,13 +964,9 @@ int main(int argc, char **argv, char **envp) {
|
||||||
|
|
||||||
/* getenv("_") is close enough to at_execfn */
|
/* getenv("_") is close enough to at_execfn */
|
||||||
execfn = argc > 0 ? argv[0] : 0;
|
execfn = argc > 0 ? argv[0] : 0;
|
||||||
varpos = 0;
|
|
||||||
for (i = 0; envp[i]; ++i) {
|
for (i = 0; envp[i]; ++i) {
|
||||||
if (envp[i][0] == '_' && envp[i][1] == '=') {
|
if (envp[i][0] == '_' && envp[i][1] == '=') {
|
||||||
execfn = envp[i] + 2;
|
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
|
/* create new bottom of stack for spawned program
|
||||||
system v abi aligns this on a 16-byte boundary
|
system v abi aligns this on a 16-byte boundary
|
||||||
grows down the alloc by poking the guard pages */
|
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);
|
sp2 = (long *)__builtin_alloca(n);
|
||||||
if ((long)sp2 & 15) ++sp2;
|
if ((long)sp2 & 15) ++sp2;
|
||||||
for (; n > 0; n -= pagesz) {
|
for (; n > 0; n -= pagesz) {
|
||||||
|
@ -991,12 +986,6 @@ int main(int argc, char **argv, char **envp) {
|
||||||
memmove(sp2, sp, (auxv - sp) * sizeof(long));
|
memmove(sp2, sp, (auxv - sp) * sizeof(long));
|
||||||
argv = (char **)(sp2 + 1);
|
argv = (char **)(sp2 + 1);
|
||||||
envp = (char **)(sp2 + 1 + argc + 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);
|
auxv = (long *)(envp + i + 1);
|
||||||
sp = sp2;
|
sp = sp2;
|
||||||
|
|
||||||
|
@ -1061,12 +1050,6 @@ int main(int argc, char **argv, char **envp) {
|
||||||
}
|
}
|
||||||
pe = ebuf->buf + rc;
|
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 */
|
/* generate some hard random data */
|
||||||
if ((rc = sys_getentropy(M->rando, sizeof(M->rando))) < 0) {
|
if ((rc = sys_getentropy(M->rando, sizeof(M->rando))) < 0) {
|
||||||
Pexit(argv[0], rc, "getentropy");
|
Pexit(argv[0], rc, "getentropy");
|
||||||
|
|
13
ape/launch.S
13
ape/launch.S
|
@ -31,17 +31,16 @@
|
||||||
//
|
//
|
||||||
// @param rdi is passed through as-is
|
// @param rdi is passed through as-is
|
||||||
// @param rsi is address of entrypoint (becomes zero)
|
// @param rsi is address of entrypoint (becomes zero)
|
||||||
// @param rdx is stack pointer (becomes zero)
|
// @param rdx is passed through as-is
|
||||||
// @param rcx is passed through as-is
|
// @param rcx is stack pointer (becomes r8)
|
||||||
// @noreturn
|
// @noreturn
|
||||||
Launch:
|
Launch:
|
||||||
#ifdef __aarch64__
|
#ifdef __aarch64__
|
||||||
|
|
||||||
mov x16,x1
|
mov x16,x1
|
||||||
mov sp,x2
|
mov sp,x3
|
||||||
mov x1,0
|
mov x1,0
|
||||||
mov x2,0
|
mov x3,x4
|
||||||
mov x3,0
|
|
||||||
mov x4,0
|
mov x4,0
|
||||||
mov x5,0
|
mov x5,0
|
||||||
mov x6,0
|
mov x6,0
|
||||||
|
@ -71,6 +70,8 @@ Launch:
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
mov %rcx,%rsp
|
||||||
|
mov %r8,%rcx
|
||||||
xor %r8d,%r8d
|
xor %r8d,%r8d
|
||||||
xor %r9d,%r9d
|
xor %r9d,%r9d
|
||||||
xor %r10d,%r10d
|
xor %r10d,%r10d
|
||||||
|
@ -79,8 +80,6 @@ Launch:
|
||||||
xor %r13d,%r13d
|
xor %r13d,%r13d
|
||||||
xor %r14d,%r14d
|
xor %r14d,%r14d
|
||||||
xor %r15d,%r15d
|
xor %r15d,%r15d
|
||||||
mov %rdx,%rsp
|
|
||||||
xor %edx,%edx
|
|
||||||
push %rsi
|
push %rsi
|
||||||
xor %esi,%esi
|
xor %esi,%esi
|
||||||
xor %ebp,%ebp
|
xor %ebp,%ebp
|
||||||
|
|
88
ape/loader.c
88
ape/loader.c
|
@ -87,6 +87,8 @@
|
||||||
#define MIN(X, Y) ((Y) > (X) ? (X) : (Y))
|
#define MIN(X, Y) ((Y) > (X) ? (X) : (Y))
|
||||||
#define MAX(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 SupportsLinux() (SUPPORT_VECTOR & LINUX)
|
||||||
#define SupportsXnu() (SUPPORT_VECTOR & XNU)
|
#define SupportsXnu() (SUPPORT_VECTOR & XNU)
|
||||||
#define SupportsFreebsd() (SUPPORT_VECTOR & FREEBSD)
|
#define SupportsFreebsd() (SUPPORT_VECTOR & FREEBSD)
|
||||||
|
@ -212,17 +214,18 @@ struct PathSearcher {
|
||||||
const char *name;
|
const char *name;
|
||||||
const char *syspath;
|
const char *syspath;
|
||||||
unsigned long namelen;
|
unsigned long namelen;
|
||||||
char path[1024];
|
char path[PATH_MAX];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ApeLoader {
|
struct ApeLoader {
|
||||||
union ElfPhdrBuf phdr;
|
union ElfPhdrBuf phdr;
|
||||||
struct PathSearcher ps;
|
struct PathSearcher ps;
|
||||||
char path[1024];
|
char path[PATH_MAX];
|
||||||
};
|
};
|
||||||
|
|
||||||
EXTERN_C long SystemCall(long, long, long, long, long, long, long, int);
|
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 __executable_start[];
|
||||||
extern char _end[];
|
extern char _end[];
|
||||||
|
@ -239,12 +242,13 @@ static int StrCmp(const char *l, const char *r) {
|
||||||
return (l[i] & 255) - (r[i] & 255);
|
return (l[i] & 255) - (r[i] & 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *BaseName(const char *s) {
|
#if 0
|
||||||
int c;
|
|
||||||
const char *b = "";
|
static const char *StrRChr(const char *s, int c) {
|
||||||
|
const char *b = 0;
|
||||||
if (s) {
|
if (s) {
|
||||||
while ((c = *s++)) {
|
for (; *s; ++s) {
|
||||||
if (c == '/') {
|
if (*s == c) {
|
||||||
b = s;
|
b = s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,6 +256,13 @@ static const char *BaseName(const char *s) {
|
||||||
return b;
|
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) {
|
static void Bzero(void *a, unsigned long n) {
|
||||||
long z;
|
long z;
|
||||||
char *p, *e;
|
char *p, *e;
|
||||||
|
@ -343,7 +354,7 @@ static char *Utox(char p[19], unsigned long x) {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *Utoa(char p[21], unsigned long x) {
|
static char *Utoa(char p[20], unsigned long x) {
|
||||||
char t;
|
char t;
|
||||||
unsigned long i, a, b;
|
unsigned long i, a, b;
|
||||||
i = 0;
|
i = 0;
|
||||||
|
@ -534,6 +545,40 @@ __attribute__((__noreturn__)) static void Pexit(int os, const char *c, int rc,
|
||||||
Exit(127, os);
|
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) {
|
static char AccessCommand(struct PathSearcher *ps, unsigned long pathlen) {
|
||||||
if (pathlen + 1 + ps->namelen + 1 > sizeof(ps->path)) return 0;
|
if (pathlen + 1 + ps->namelen + 1 > sizeof(ps->path)) return 0;
|
||||||
if (pathlen && ps->path[pathlen - 1] != '/') ps->path[pathlen++] = '/';
|
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,
|
__attribute__((__noreturn__)) static void Spawn(int os, const char *exe,
|
||||||
long *sp, unsigned long pagesz,
|
char *path, int fd, long *sp,
|
||||||
|
unsigned long pagesz,
|
||||||
struct ElfEhdr *e,
|
struct ElfEhdr *e,
|
||||||
struct ElfPhdr *p) {
|
struct ElfPhdr *p) {
|
||||||
long rc;
|
long rc;
|
||||||
|
@ -757,12 +803,12 @@ __attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd,
|
||||||
Msyscall(dynbase + code, codesize, os);
|
Msyscall(dynbase + code, codesize, os);
|
||||||
|
|
||||||
/* call program entrypoint */
|
/* 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,
|
static const char *TryElf(struct ApeLoader *M, union ElfEhdrBuf *ebuf,
|
||||||
const char *exe, int fd, long *sp, long *auxv,
|
const char *exe, char *path, int fd, long *sp,
|
||||||
unsigned long pagesz, int os) {
|
long *auxv, unsigned long pagesz, int os) {
|
||||||
long i, rc;
|
long i, rc;
|
||||||
unsigned size;
|
unsigned size;
|
||||||
struct ElfEhdr *e;
|
struct ElfEhdr *e;
|
||||||
|
@ -877,7 +923,7 @@ static const char *TryElf(struct ApeLoader *M, union ElfEhdrBuf *ebuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* we're now ready to load */
|
/* 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) {
|
__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)");
|
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");
|
||||||
|
} 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) {
|
} else if ((rc = Pread(fd, ebuf->buf, sizeof(ebuf->buf), 0, os)) < 0) {
|
||||||
Pexit(os, exe, rc, "read");
|
Pexit(os, exe, rc, "read");
|
||||||
} else if ((unsigned long)rc < sizeof(ebuf->ehdr)) {
|
} 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;
|
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
|
/* ape intended behavior
|
||||||
1. if ape, will scan shell script for elf printf statements
|
1. if ape, will scan shell script for elf printf statements
|
||||||
2. shell script may have multiple lines producing elf headers
|
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)) {
|
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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
#include "libc/runtime/runtime.h"
|
#include "libc/runtime/runtime.h"
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
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) {
|
for (char **p = environ; *p; ++p) {
|
||||||
printf(" %s\n", *p);
|
printf("%s\n", *p);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,14 @@ static inline int IsAlpha(int c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void InitProgramExecutableNameImpl(void) {
|
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()) {
|
if (IsWindows()) {
|
||||||
int n = GetModuleFileName(0, g_prog.u.buf16, ARRAYLEN(g_prog.u.buf16));
|
int n = GetModuleFileName(0, g_prog.u.buf16, ARRAYLEN(g_prog.u.buf16));
|
||||||
for (int i = 0; i < n; ++i) {
|
for (int i = 0; i < n; ++i) {
|
||||||
|
@ -69,21 +76,44 @@ static inline void InitProgramExecutableNameImpl(void) {
|
||||||
g_prog.u.buf16[2] = '/';
|
g_prog.u.buf16[2] = '/';
|
||||||
}
|
}
|
||||||
tprecode16to8(g_prog.u.buf, sizeof(g_prog.u.buf), g_prog.u.buf16);
|
tprecode16to8(g_prog.u.buf, sizeof(g_prog.u.buf), g_prog.u.buf16);
|
||||||
return;
|
goto UseBuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
char c, *q;
|
|
||||||
if (IsMetal()) {
|
if (IsMetal()) {
|
||||||
q = APE_COM_NAME;
|
__program_executable_name = APE_COM_NAME;
|
||||||
goto CopyString;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* the new-style loader supplies the full program path as the first
|
b = g_prog.u.buf;
|
||||||
environment variable. in the spirit of Postel's Law ("be liberal
|
n = sizeof(g_prog.u.buf) - 1;
|
||||||
in what you accept"), we use __getenv to read it. */
|
if (IsFreebsd() || IsNetbsd()) {
|
||||||
if ((q = __getenv(__envp, "COSMOPOLITAN_PROGRAM_EXECUTABLE").s)) {
|
int cmd[4];
|
||||||
strlcpy(g_prog.u.buf, q, sizeof(g_prog.u.buf));
|
cmd[0] = CTL_KERN;
|
||||||
return;
|
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
|
// if argv[0] exists then turn it into an absolute path. we also try
|
||||||
|
@ -107,42 +137,18 @@ static inline void InitProgramExecutableNameImpl(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*p = 0;
|
*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 = WRITE32LE(p, READ32LE(".com"));
|
||||||
*p = 0;
|
*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
|
/* the previous loader supplied the full program path as the first
|
||||||
for (char **ep = __envp; (q = *ep); ++ep) {
|
environment variable. we also try "_". */
|
||||||
if (*q++ == '_' && *q++ == '=') {
|
if ((q = __getenv(__envp, "COSMOPOLITAN_PROGRAM_EXECUTABLE").s) ||
|
||||||
|
(q = __getenv(__envp, "_").s)) {
|
||||||
goto CopyString;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// give up and just copy argv[0] into it
|
// give up and just copy argv[0] into it
|
||||||
if ((q = __argv[0])) {
|
if ((q = __argv[0])) {
|
||||||
|
@ -155,14 +161,18 @@ static inline void InitProgramExecutableNameImpl(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*p = 0;
|
*p = 0;
|
||||||
return;
|
goto UseBuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we don't even have that then empty the string
|
// if we don't even have that then empty the string
|
||||||
|
UseEmpty:
|
||||||
g_prog.u.buf[0] = 0;
|
g_prog.u.buf[0] = 0;
|
||||||
|
|
||||||
|
UseBuf:
|
||||||
|
__program_executable_name = g_prog.u.buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
void __InitProgramExecutableName(void) {
|
static void InitProgramExecutableName(void) {
|
||||||
int e = errno;
|
int e = errno;
|
||||||
InitProgramExecutableNameImpl();
|
InitProgramExecutableNameImpl();
|
||||||
errno = e;
|
errno = e;
|
||||||
|
@ -172,6 +182,6 @@ void __InitProgramExecutableName(void) {
|
||||||
* Returns absolute path of program.
|
* Returns absolute path of program.
|
||||||
*/
|
*/
|
||||||
char *GetProgramExecutableName(void) {
|
char *GetProgramExecutableName(void) {
|
||||||
cosmo_once(&g_prog.once, __InitProgramExecutableName);
|
cosmo_once(&g_prog.once, InitProgramExecutableName);
|
||||||
return g_prog.u.buf;
|
return __program_executable_name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,8 @@ _start:
|
||||||
// set operating system when already detected
|
// set operating system when already detected
|
||||||
1: mov %cl,__hostos(%rip)
|
1: mov %cl,__hostos(%rip)
|
||||||
|
|
||||||
|
mov %rdx,__program_executable_name(%rip)
|
||||||
|
|
||||||
// get startup timestamp as early as possible
|
// get startup timestamp as early as possible
|
||||||
// its used by --strace flag and kprintf() %T
|
// its used by --strace flag and kprintf() %T
|
||||||
rdtsc
|
rdtsc
|
||||||
|
@ -140,6 +142,8 @@ _start:
|
||||||
// should be set to zero on other platforms
|
// should be set to zero on other platforms
|
||||||
mov x1,x15
|
mov x1,x15
|
||||||
|
|
||||||
|
// third arg (x2) is the program path passed by ape-m1.c
|
||||||
|
|
||||||
// switch to c code
|
// switch to c code
|
||||||
bl cosmo
|
bl cosmo
|
||||||
.unreachable
|
.unreachable
|
||||||
|
|
21
libc/nexgen32e/program_executable_name.c
Normal file
21
libc/nexgen32e/program_executable_name.c
Normal 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;
|
|
@ -78,7 +78,7 @@ static const char *DecodeMagnum(const char *p, long *r) {
|
||||||
return *r = x, p;
|
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
|
// get startup timestamp as early as possible
|
||||||
// its used by --strace and also kprintf() %T
|
// its used by --strace and also kprintf() %T
|
||||||
|
@ -108,6 +108,7 @@ wontreturn textstartup void cosmo(long *sp, struct Syslib *m1) {
|
||||||
__envp = envp;
|
__envp = envp;
|
||||||
__auxv = auxv;
|
__auxv = auxv;
|
||||||
environ = envp;
|
environ = envp;
|
||||||
|
__program_executable_name = exename;
|
||||||
program_invocation_name = argv[0];
|
program_invocation_name = argv[0];
|
||||||
__oldstack = (intptr_t)sp;
|
__oldstack = (intptr_t)sp;
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,7 @@ extern char **__argv;
|
||||||
extern char **__envp;
|
extern char **__envp;
|
||||||
extern unsigned long *__auxv;
|
extern unsigned long *__auxv;
|
||||||
extern intptr_t __oldstack;
|
extern intptr_t __oldstack;
|
||||||
|
extern char *__program_executable_name;
|
||||||
extern uint64_t __nosync;
|
extern uint64_t __nosync;
|
||||||
extern int __strace;
|
extern int __strace;
|
||||||
extern int __ftrace;
|
extern int __ftrace;
|
||||||
|
|
|
@ -28,25 +28,26 @@
|
||||||
#include "libc/testlib/testlib.h"
|
#include "libc/testlib/testlib.h"
|
||||||
|
|
||||||
static char *self;
|
static char *self;
|
||||||
static bool skipcosmotests;
|
|
||||||
|
|
||||||
void SetUp(void) {
|
|
||||||
self = GetProgramExecutableName();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetUpOnce(void) {
|
void SetUpOnce(void) {
|
||||||
if (!getenv("COSMOPOLITAN_PROGRAM_EXECUTABLE")) {
|
self = GetProgramExecutableName();
|
||||||
fprintf(stderr,
|
|
||||||
"warning: old ape loader detected; skipping some tests %m\n");
|
|
||||||
skipcosmotests = true;
|
|
||||||
}
|
|
||||||
testlib_enable_tmp_setup_teardown();
|
testlib_enable_tmp_setup_teardown();
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((__constructor__)) static void Child(int argc, char *argv[]) {
|
__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")) {
|
if (argc >= 2 && !strcmp(argv[1], "Child")) {
|
||||||
ASSERT_EQ(3, argc);
|
ASSERT_EQ(argc, 4);
|
||||||
EXPECT_STREQ(argv[2], GetProgramExecutableName());
|
EXPECT_STREQ(argv[2], GetProgramExecutableName());
|
||||||
|
if (!skiparg0tests) {
|
||||||
|
EXPECT_STREQ(argv[3], argv[0]);
|
||||||
|
}
|
||||||
exit(g_testlib_failed);
|
exit(g_testlib_failed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,33 +60,33 @@ TEST(GetProgramExecutableName, ofThisFile) {
|
||||||
|
|
||||||
TEST(GetProgramExecutableName, nullEnv) {
|
TEST(GetProgramExecutableName, nullEnv) {
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
execve(self, (char *[]){self, "Child", self, 0}, (char *[]){0});
|
execve(self, (char *[]){self, "Child", self, self, 0}, (char *[]){0});
|
||||||
abort();
|
abort();
|
||||||
EXITS(0);
|
EXITS(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GetProramExecutableName, weirdArgv0NullEnv) {
|
TEST(GetProramExecutableName, weirdArgv0NullEnv) {
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
execve(self, (char *[]){"hello", "Child", self, 0}, (char *[]){0});
|
execve(self, (char *[]){"hello", "Child", self, "hello", 0}, (char *[]){0});
|
||||||
abort();
|
abort();
|
||||||
EXITS(0);
|
EXITS(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GetProgramExecutableName, weirdArgv0CosmoVar) {
|
TEST(GetProgramExecutableName, weirdArgv0CosmoVar) {
|
||||||
if (skipcosmotests) return;
|
|
||||||
char buf[32 + PATH_MAX];
|
char buf[32 + PATH_MAX];
|
||||||
stpcpy(stpcpy(buf, "COSMOPOLITAN_PROGRAM_EXECUTABLE="), self);
|
stpcpy(stpcpy(buf, "COSMOPOLITAN_PROGRAM_EXECUTABLE="), self);
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
execve(self, (char *[]){"hello", "Child", self, 0}, (char *[]){buf, 0});
|
execve(self, (char *[]){"hello", "Child", self, "hello", 0},
|
||||||
|
(char *[]){buf, 0});
|
||||||
abort();
|
abort();
|
||||||
EXITS(0);
|
EXITS(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GetProgramExecutableName, weirdArgv0WrongCosmoVar) {
|
TEST(GetProgramExecutableName, weirdArgv0WrongCosmoVar) {
|
||||||
if (skipcosmotests) return;
|
|
||||||
char *bad = "COSMOPOLITAN_PROGRAM_EXECUTABLE=hi";
|
char *bad = "COSMOPOLITAN_PROGRAM_EXECUTABLE=hi";
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
execve(self, (char *[]){"hello", "Child", self, 0}, (char *[]){bad, 0});
|
execve(self, (char *[]){"hello", "Child", self, "hello", 0},
|
||||||
|
(char *[]){bad, 0});
|
||||||
abort();
|
abort();
|
||||||
EXITS(0);
|
EXITS(0);
|
||||||
}
|
}
|
||||||
|
@ -104,13 +105,7 @@ TEST(GetProgramExecutableName, movedSelf) {
|
||||||
ASSERT_NE(NULL, getcwd(buf, BUFSIZ - 5));
|
ASSERT_NE(NULL, getcwd(buf, BUFSIZ - 5));
|
||||||
stpcpy(buf + strlen(buf), "/test");
|
stpcpy(buf + strlen(buf), "/test");
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
execve(buf, (char *[]){"hello", "Child", buf, 0}, (char *[]){0});
|
execve(buf, (char *[]){"hello", "Child", buf, "hello", 0}, (char *[]){0});
|
||||||
abort();
|
abort();
|
||||||
EXITS(0);
|
EXITS(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void __InitProgramExecutableName(void);
|
|
||||||
|
|
||||||
BENCH(GetProgramExecutableName, bench) {
|
|
||||||
EZBENCH2("Init", donothing, __InitProgramExecutableName());
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue