diff --git a/configure.ac b/configure.ac index c60d35741..fb3e22373 100644 --- a/configure.ac +++ b/configure.ac @@ -467,6 +467,8 @@ AC_CHECK_FUNCS([ \ fork \ getpeereid \ getpeerucred \ + getresgid \ + getresuid \ grantpt \ kill \ mktime \ diff --git a/lib/roken/issuid.c b/lib/roken/issuid.c index 7a77b3834..9f8227bf9 100644 --- a/lib/roken/issuid.c +++ b/lib/roken/issuid.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998 - 2001 Kungliga Tekniska Högskolan + * Copyright (c) 1998 - 2017 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * @@ -57,28 +57,114 @@ ((__GLIBC << 16) + GLIBC_MINOR >= ((maj) << 16) + (min)) #endif +/* + * Do change this check in order to manually test rk_getauxval() for + * older glibcs. + */ #if HAVE_GLIBC_API_VERSION_SUPPORT(2, 19) #define GETAUXVAL_SETS_ERRNO #endif #endif -#ifdef HAVE_GETAUXVAL -static unsigned long +/** + * Like the nearly-standard getauxval(), but reads through + * /proc/self/auxv if it exists (this works on Linux, and, by code + * inspection, on FreeBSD, but not Solaris/Illumos, where the auxv type + * is an int and the value is a union of long, data pointer, and + * function pointer), otherwise it sets errno to ENOENT and returns + * zero. If the auxval is not found returns zero and always sets errno + * to ENOENT. Otherwise if auxval is found it leaves errno as it was, + * even if the value is zero. + * + * @return The value of the ELF auxiliary value for the given type. + */ +ROKEN_LIB_FUNCTION unsigned long ROKEN_LIB_CALL +rk_getprocauxval(unsigned long type) +{ + static int has_proc_auxv = 1; + unsigned long a[2]; + ssize_t bytes; + int save_errno = errno; + int fd; + + if (!has_proc_auxv) { + errno = ENOENT; + return 0; + } + + if ((fd = open("/proc/self/auxv", O_RDONLY)) == -1) { + if (errno == ENOENT) + has_proc_auxv = 0; + errno = ENOENT; + return 0; + } + + /* FIXME: Make this work on Illumos */ + do { + if ((bytes = read(fd, a, sizeof(a))) != sizeof(a)) + break; + if (a[0] == type) { + (void) close(fd); + errno = save_errno; + return a[1]; + } + } while (bytes == sizeof(a) && (a[0] != 0 || a[1] != 0)); + + (void) close(fd); + errno = ENOENT; + return 0; +} + +/** + * Like the nearly-standard getauxval(). If the auxval is not found + * returns zero and always sets errno to ENOENT. Otherwise if auxval is + * found it leaves errno as it was, even if the value is zero. + * + * @return The value of the ELF auxiliary value for the given type. + */ +ROKEN_LIB_FUNCTION unsigned long ROKEN_LIB_CALL rk_getauxval(unsigned long type) { - errno = 0; +#ifdef HAVE_GETAUXVAL #ifdef GETAUXVAL_SETS_ERRNO return getauxval(type); #else - unsigned long ret = getauxval(type); + unsigned long ret; + unsigned long ret2; + static int getauxval_sets_errno = -1; + int save_errno = errno; - if (ret == 0) - errno = ENOENT; - return ret; + errno = 0; + ret = getauxval(type); + if (ret != 0 || errno == ENOENT || getauxval_sets_errno == 1) { + if (ret != 0) + errno = save_errno; + else if (getauxval_sets_errno && errno == 0) + errno = save_errno; + return ret; + } + + if (!getauxval_sets_errno) { + errno = save_errno; + return rk_getprocauxval(type); + } + + errno = 0; + ret2 = getauxval(~type); /* Hacky, quite hacky */ + if (ret2 == 0 && errno == ENOENT) { + getauxval_sets_errno = 1; + errno = save_errno; + return ret; /* Oh, it does set errno. Good! */ + } + + errno = save_errno; + getauxval_sets_errno = 0; + return rk_getprocauxval(type); +#endif +#else + return rk_getprocauxval(type); #endif } -#define USE_RK_GETAUXVAL -#endif /** * Returns non-zero if the caller's process started as set-uid or @@ -118,35 +204,59 @@ issuid(void) * 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. + * issetugid() functionality. Other useful (and used here) auxv + * types include: AT_SECURE (Linux) and the path to the program + * exec'ed. None of this applies to statically-linked programs + * though. * - * Where available, we use the ELF auxilliary vector as a fallback - * if issetugid() is not available. + * Where available, we use the ELF auxilliary vector before trying + * issetugid(). * - * All of this is as of late March 2015, and might become stale in + * All of this is as of late March 2017, 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))) + static int we_are_suid = -1; + int save_errno = errno; +#if (defined(AT_EUID) && defined(AT_UID)) || (defined(AT_EGID) && defined(AT_GID)) int seen = 0; #endif + if (we_are_suid >= 0) + return we_are_suid; + +#ifdef AT_SECURE + /* + * AT_SECURE is set if the program was set-id or gained any kind of + * privilege in a similar way. + */ + errno = 0; + if (rk_getauxval(AT_SECURE) != 0) { + errno = save_errno; + return we_are_suid = 1; + } + else if (errno == 0) { + errno = save_errno; + return we_are_suid = 0; + } +#endif + #if defined(AT_EUID) && defined(AT_UID) { unsigned long euid; unsigned long uid; + errno = 0; euid = rk_getauxval(AT_EUID); if (errno == 0) seen |= 1; + errno = 0; uid = rk_getauxval(AT_UID); if (errno == 0) seen |= 2; - if (euid != uid) - return 1; + if (euid != uid) { + errno = save_errno; + return we_are_suid = 1; + } } #endif #if defined(AT_EGID) && defined(AT_GID) @@ -154,62 +264,156 @@ issuid(void) unsigned long egid; unsigned long gid; + errno = 0; egid = rk_getauxval(AT_EGID); if (errno == 0) seen |= 4; + errno = 0; gid = rk_getauxval(AT_GID); if (errno == 0) seen |= 8; - if (egid != gid) - return 2; + if (egid != gid) { + errno = save_errno; + return we_are_suid = 1; + } } #endif -#ifdef AT_SECURE - /* AT_SECURE is set if the program was set-id. */ - if (rk_getauxval(AT_SECURE) != 0) - return 1; -#endif + errno = save_errno; -#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. + * This pre-processor condition could be all &&s, but that could + * cause a warning that seen is set but never used. + * + * In practice if any one of these four macros is defined then all + * of them will be. */ -#endif /* USE_RK_GETAUXVAL */ +#if (defined(AT_EUID) && defined(AT_UID)) || (defined(AT_EGID) && defined(AT_GID)) + if (seen == 15) { + errno = save_errno; + return we_are_suid = 0; + } +#endif #if defined(HAVE_ISSETUGID) /* - * If we have issetugid(), use it. + * If we have issetugid(), use it. Illumos' and OpenBSD's + * issetugid() works correctly. * - * We may lose on some BSDs. This manifests as, for example, - * gss_store_cred() not honoring KRB5CCNAME. + * On NetBSD and FreeBSD, however, issetugid() returns non-zero even + * if the process started as root, not-set-uid, and then later + * called seteuid(), for example, but in that case we'd want to + * trust the environ! So if issetugid() > 0 we want to do something + * else. See below. */ - return issetugid(); + if (issetugid() == 0) + return we_are_suid = 0; #endif /* USE_RK_GETAUXVAL */ +#if defined(AT_EXECFN) || defined(AT_EXECPATH) + + /* + * There's an auxval by which to find the path of the program this + * process exec'ed. + * + * Linux calls this AT_EXECFN. FreeBSD calls it AT_EXECPATH. NetBSD + * and Illumos call it AT_SUN_EXECNAME. + * + * We can stat it. If the program did a chroot() and the chroot has + * a program with the same path but not set-uid/set-gid, of course, + * we lose here. But a) that's a bit of a stretch, b) there's not + * much more we can do here. + */ +#if defined(AT_EXECFN) && !defined(AT_EXECPATH) +#define AT_EXECPATH AT_EXECFN +#endif +#if defined(AT_SUN_EXECNAME) && !defined(AT_EXECPATH) +#define AT_EXECPATH AT_EXECFN +#endif + { + unsigned long p = getauxval(AT_EXECPATH); + struct stat st; + + if (p != 0 && *(const char *)p == '/' && + stat((const char *)p, &st) == 0) { + if ((st.st_mode & S_ISUID) || (st.st_mode & S_ISGID)) { + errno = save_errno; + return we_are_suid = 1; + } + errno = save_errno; + return we_are_suid = 0; + } + } +#endif + + /* + * Fall through if we have rk_getauxval() but we didn't have (or + * don't know if we don't have) the aux entries that we needed. + * We're done with it. + */ + +#if defined(HAVE_ISSETUGID) + errno = save_errno; + return we_are_suid = 1; +#else + /* * 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. + * But who knows what that might break where users link statically + * (so no auxv), say. Also, on Windows we should always return 0. + * + * For now we stick to returning zero by default. We've been rather + * heroic above trying to find out if we're suid. */ +#if defined(HAVE_GETRESUID) + /* + * If r/e/suid are all the same then chances are very good we did + * not start as set-uid. Though this could be a login program that + * started out as privileged and is calling Heimdal "as the user". + * + * Again, such a program would have to be statically linked to get + * here. + */ + { + uid_t r, e, s; + if (getresuid(&r, &e, &s) == 0) { + if (r != e || r != s) { + errno = save_errno; + return we_are_suid = 1; + } + } + } +#endif +#if defined(HAVE_GETRESGID) + { + gid_t r, e, s; + if (getresgid(&r, &e, &s) == 0) { + if (r != e || r != s) { + errno = save_errno; + return we_are_suid = 1; + } + } + } +#endif +#if defined(HAVE_GETRESUID) && defined(HAVE_GETRESGID) + errno = save_errno; + return we_are_suid = 0; + +#else /* avoid compiler warnings about dead code */ + #if defined(HAVE_GETUID) && defined(HAVE_GETEUID) if (getuid() != geteuid()) - return 1; + return we_are_suid = 1; #endif #if defined(HAVE_GETGID) && defined(HAVE_GETEGID) if (getgid() != getegid()) - return 2; + return we_are_suid = 1; #endif - return 0; +#endif /* !defined(HAVE_GETRESUID) || !defined(HAVE_GETRESGID) */ + + errno = save_errno; + return we_are_suid = 0; +#endif /* !defined(HAVE_ISSETUGID) */ } diff --git a/lib/roken/roken.h.in b/lib/roken/roken.h.in index 1a9db6356..3e9063ea6 100644 --- a/lib/roken/roken.h.in +++ b/lib/roken/roken.h.in @@ -827,6 +827,12 @@ ROKEN_LIB_FUNCTION ssize_t ROKEN_LIB_CALL ROKEN_LIB_FUNCTION ssize_t ROKEN_LIB_CALL net_read (rk_socket_t, void *, size_t); +ROKEN_LIB_FUNCTION unsigned long ROKEN_LIB_CALL + rk_getprocauxval(unsigned long); + +ROKEN_LIB_FUNCTION unsigned long ROKEN_LIB_CALL + rk_getauxval(unsigned long); + ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL issuid(void); diff --git a/lib/roken/version-script.map b/lib/roken/version-script.map index ccd5925d2..3864d7eb5 100644 --- a/lib/roken/version-script.map +++ b/lib/roken/version-script.map @@ -68,10 +68,12 @@ HEIMDAL_ROKEN_1.0 { rk_freeifaddrs; rk_gai_strerror; rk_getaddrinfo; + rk_getauxval; rk_getifaddrs; rk_getipnodebyaddr; rk_getipnodebyname; rk_getnameinfo; + rk_getprocauxval; rk_getprogname; rk_glob; rk_globfree;