mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-05-23 05:42:29 +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,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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue