From ba2b8b8c68fb3e003b7b4f91a9fb3d3e962e5d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C5=8Dshin?= Date: Mon, 18 Dec 2023 14:56:20 -0500 Subject: [PATCH] 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. --- libc/calls/getprogramexecutablename.greg.c | 37 ++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/libc/calls/getprogramexecutablename.greg.c b/libc/calls/getprogramexecutablename.greg.c index 20db37e23..fd57e4ae1 100644 --- a/libc/calls/getprogramexecutablename.greg.c +++ b/libc/calls/getprogramexecutablename.greg.c @@ -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; }