sanity-check set-id interpreter script paths

The check is fairly stringent; we do it in a constructor, and we make
sure that there actually is an open fd higher than 2 that matches the
passed path.

There is an argument to be made for trapping in the constructor here,
and there is also an argument to be made for failing open like we did
previously. We pick a middle ground: GetProgramExecutableName returns
empty string. Logic being that ensuring the sanity of whatever set-id
script mechanism user is running is above our pay grade, but the path
TOCTOU between loader and binary is not.
This commit is contained in:
Jōshin 2023-12-18 14:56:20 -05:00
parent 2a11a09d98
commit ba2b8b8c68
No known key found for this signature in database

View file

@ -23,6 +23,7 @@
#include "libc/cosmo.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/fmt/libgen.h"
#include "libc/intrin/getenv.internal.h"
#include "libc/serialize.h"
@ -32,12 +33,14 @@
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/ok.h"
#define CTL_KERN 1
#define KERN_PROC 14
#define KERN_PROC_PATHNAME_FREEBSD 12
#define KERN_PROC_PATHNAME_NETBSD 5
#define DEV_FD "/dev/fd/"
static struct {
atomic_uint once;
@ -47,6 +50,8 @@ static struct {
} u;
} g_prog;
static bool g_ape_loaded;
static inline int IsAlpha(int c) {
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
}
@ -104,6 +109,29 @@ static int TryPath(const char *q, int com) {
return 0;
}
__attribute__((__constructor__)) static void setugidNameCheck(void) {
char *end;
unsigned long rc;
if (__program_executable_name) {
g_ape_loaded = true;
if (issetugid()) {
/* we are running as a set-id interpreter script. this is highly unusual.
it means either someone installed their ape loader set-id, or they are
running a system that supports secure set-id interpreter scripts via a
/dev/fd/ path. check for the latter and allow that. otherwise, set the
__program_executable_name to NULL since there is an unavoidable TOCTOU
problem between the kernel and the loader. */
if ((!IsNetbsd() && !IsOpenbsd() && !IsXnu()) /* any others? */ ||
0 != strncmp(DEV_FD, __program_executable_name, sizeof(DEV_FD) - 1) ||
(rc = strtoul(__program_executable_name + sizeof(DEV_FD) - 1, &end,
10)) > INT_MAX ||
*end || rc < 3 || fcntl((int)rc, F_GETFD) == -1) {
__program_executable_name = NULL;
}
}
}
}
static inline void InitProgramExecutableNameImpl(void) {
size_t n;
ssize_t got;
@ -133,8 +161,13 @@ static inline void InitProgramExecutableNameImpl(void) {
return;
}
// loader passed us a path. it may be relative.
if (__program_executable_name) {
if (g_ape_loaded) {
/* the loader passed us a path. it may be relative. */
if (!__program_executable_name) {
/* we rejected the loader path because we are running set-id and it
looked insecure. use the empty string. */
goto UseEmpty;
}
if (*__program_executable_name == '/') {
return;
}