mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-06-26 14:28:30 +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>
|
||||
|
||||
#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");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue