diff --git a/cf/roken-frag.m4 b/cf/roken-frag.m4 index faf8daf68..452c4fd07 100644 --- a/cf/roken-frag.m4 +++ b/cf/roken-frag.m4 @@ -71,6 +71,7 @@ AC_CHECK_HEADERS([\ search.h \ shadow.h \ stdint.h \ + sys/auxv.h \ sys/bswap.h \ sys/errno.h \ sys/ioctl.h \ @@ -184,6 +185,7 @@ AC_CHECK_FUNCS([ \ asprintf \ atexit \ cgetent \ + getauxval \ getconfattr \ getprogname \ getrlimit \ diff --git a/lib/roken/issuid.c b/lib/roken/issuid.c index ea0db803e..7a77b3834 100644 --- a/lib/roken/issuid.c +++ b/lib/roken/issuid.c @@ -33,24 +33,183 @@ #include +#ifdef HAVE_SYS_AUXV_H +#include +#endif + +#include + #include "roken.h" +/* NetBSD calls AT_UID AT_RUID. Everyone else calls it AT_UID. */ +#if defined(AT_EUID) && defined(AT_RUID) && !defined(AT_UID) +#define AT_UID AT_RUID +#endif +#if defined(AT_EGID) && defined(AT_RGID) && !defined(AT_GID) +#define AT_GID AT_RGID +#endif + +#ifdef __GLIBC__ +#ifdef __GLIBC_PREREQ +#define HAVE_GLIBC_API_VERSION_SUPPORT(maj, min) __GLIBC_PREREQ(maj, min) +#else +#define HAVE_GLIBC_API_VERSION_SUPPORT(maj, min) \ + ((__GLIBC << 16) + GLIBC_MINOR >= ((maj) << 16) + (min)) +#endif + +#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 19) +#define GETAUXVAL_SETS_ERRNO +#endif +#endif + +#ifdef HAVE_GETAUXVAL +static unsigned long +rk_getauxval(unsigned long type) +{ + errno = 0; +#ifdef GETAUXVAL_SETS_ERRNO + return getauxval(type); +#else + unsigned long ret = getauxval(type); + + if (ret == 0) + errno = ENOENT; + return ret; +#endif +} +#define USE_RK_GETAUXVAL +#endif + +/** + * Returns non-zero if the caller's process started as set-uid or + * set-gid (and therefore the environment cannot be trusted). + * + * @return Non-zero if the environment is not trusted. + */ ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL issuid(void) { + /* + * We want to use issetugid(), but issetugid() is not the same on + * all OSes. + * + * On Illumos derivatives, OpenBSD, and Solaris issetugid() returns + * true IFF the program exec()ed was set-uid or set-gid. + * + * On NetBSD and FreeBSD issetugid() returns true if the program + * exec()ed was set-uid or set-gid, or if the process has switched + * UIDs/GIDs or otherwise changed privileges or is a descendant of + * such a process and has not exec()ed since. + * + * What we want here is to know only if the program exec()ed was + * set-uid or set-gid, so we can decide whether to trust the + * enviroment variables. We don't care if this was a process that + * started as root and later changed UIDs/privs whatever: since it + * started out as privileged, it inherited an environment from a + * privileged pre-exec self, and so on, so the environment is + * trusted. + * + * Therefore the FreeBSD/NetBSD issetugid() does us no good. + * + * Linux, meanwhile, has no issetugid() (at least glibc doesn't + * anyways). + * + * Systems that support ELF put an "auxilliary vector" on the stack + * prior to starting the RTLD, and this vector includes (optionally) + * information about the process' EUID, RUID, EGID, RGID, and so on + * at the time of exec(), which we can use to construct proper + * issetugid() functionality. + * + * Where available, we use the ELF auxilliary vector as a fallback + * if issetugid() is not available. + * + * All of this is as of late March 2015, and might become stale in + * the future. + */ + +#ifdef USE_RK_GETAUXVAL + /* If we have getauxval(), use that */ + +#if (defined(AT_EUID) && defined(AT_UID) || (defined(AT_EGID) && defined(AT_GID))) + int seen = 0; +#endif + +#if defined(AT_EUID) && defined(AT_UID) + { + unsigned long euid; + unsigned long uid; + + euid = rk_getauxval(AT_EUID); + if (errno == 0) + seen |= 1; + uid = rk_getauxval(AT_UID); + if (errno == 0) + seen |= 2; + if (euid != uid) + return 1; + } +#endif +#if defined(AT_EGID) && defined(AT_GID) + { + unsigned long egid; + unsigned long gid; + + egid = rk_getauxval(AT_EGID); + if (errno == 0) + seen |= 4; + gid = rk_getauxval(AT_GID); + if (errno == 0) + seen |= 8; + if (egid != gid) + return 2; + } +#endif +#ifdef AT_SECURE + /* AT_SECURE is set if the program was set-id. */ + if (rk_getauxval(AT_SECURE) != 0) + return 1; +#endif + +#if (defined(AT_EUID) && defined(AT_UID) || (defined(AT_EGID) && defined(AT_GID))) + if (seen == 15) + return 0; +#endif + + /* rk_getauxval() does set errno */ + if (errno == 0) + return 0; + /* + * Fall through if we have getauxval() but we didn't have (or don't + * know if we don't have) the aux entries that we needed. + */ +#endif /* USE_RK_GETAUXVAL */ + #if defined(HAVE_ISSETUGID) + /* + * If we have issetugid(), use it. + * + * We may lose on some BSDs. This manifests as, for example, + * gss_store_cred() not honoring KRB5CCNAME. + */ return issetugid(); -#else /* !HAVE_ISSETUGID */ +#endif /* USE_RK_GETAUXVAL */ + + /* + * Paranoia: for extra safety we ought to default to returning 1. + * But who knows what that might break where users link statically + * and use a.out, say. Also, on Windows we should always return 0. + * + * For now we stick to returning zero by default. + */ #if defined(HAVE_GETUID) && defined(HAVE_GETEUID) - if(getuid() != geteuid()) + if (getuid() != geteuid()) return 1; #endif #if defined(HAVE_GETGID) && defined(HAVE_GETEGID) - if(getgid() != getegid()) + if (getgid() != getegid()) return 2; #endif return 0; -#endif /* HAVE_ISSETUGID */ }