blocxx

Secure.cpp

Go to the documentation of this file.
00001 /*******************************************************************************
00002 * Copyright (C) 2005, Quest Software, Inc. All rights reserved.
00003 * Copyright (C) 2006, Novell, Inc. All rights reserved.
00004 * 
00005 * Redistribution and use in source and binary forms, with or without
00006 * modification, are permitted provided that the following conditions are met:
00007 * 
00008 *     * Redistributions of source code must retain the above copyright notice,
00009 *       this list of conditions and the following disclaimer.
00010 *     * Redistributions in binary form must reproduce the above copyright
00011 *       notice, this list of conditions and the following disclaimer in the
00012 *       documentation and/or other materials provided with the distribution.
00013 *     * Neither the name of 
00014 *       Quest Software, Inc., 
00015 *       nor Novell, Inc., 
00016 *       nor the names of its contributors or employees may be used to 
00017 *       endorse or promote products derived from this software without 
00018 *       specific prior written permission.
00019 * 
00020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00021 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00022 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00023 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00024 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00025 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00026 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00027 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00028 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00029 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00030 * POSSIBILITY OF SUCH DAMAGE.
00031 *******************************************************************************/
00032 
00033 
00034 #include "blocxx/BLOCXX_config.h"
00035 #include "blocxx/Array.hpp"
00036 #include "blocxx/Secure.hpp"
00037 #include "blocxx/FileSystem.hpp"
00038 #include "blocxx/String.hpp"
00039 #include "blocxx/Paths.hpp"
00040 #include "blocxx/Format.hpp"
00041 #include "blocxx/LazyGlobal.hpp"
00042 #ifdef BLOCXX_HAVE_DIRENT_H
00043 #include <dirent.h>
00044 #endif
00045 #include <fcntl.h>
00046 #ifndef BLOCXX_WIN32
00047 #include <grp.h>
00048 #endif
00049 #include <limits.h>
00050 #ifdef BLOCXX_HAVE_PWD_H
00051 #include <pwd.h>
00052 #endif
00053 #ifdef BLOCXX_HAVE_SYS_PARAM_H
00054 #include <sys/param.h>
00055 #endif
00056 #include <sys/types.h>
00057 #include <sys/stat.h>
00058 #ifdef BLOCXX_HAVE_UNISTD_H
00059 #include <unistd.h>
00060 #endif
00061 #include <cstdlib>
00062 #include <cstdio>
00063 #include <cerrno>
00064 #include <vector>
00065 #include <algorithm>
00066 
00067 #ifdef AIX
00068 #include "blocxx/StringBuffer.hpp"
00069 #include "blocxx/NonRecursiveMutex.hpp"
00070 #include "blocxx/NonRecursiveMutexLock.hpp"
00071 #include <fstream>
00072 #include <cctype>
00073 #endif
00074 
00075 #if defined(BLOCXX_NO_SETRESGID_PROTO) && defined(BLOCXX_HAVE_SETRESGID)
00076 extern "C" { int setresgid(gid_t rgid, gid_t egid, gid_t sgid); }
00077 #endif
00078 
00079 #if defined(BLOCXX_NO_SETRESUID_PROTO) && defined(BLOCXX_HAVE_SETRESUID)
00080 extern "C" { int setresuid(uid_t ruid, uid_t euid, uid_t suid); }
00081 #endif
00082 
00083 using namespace blocxx;
00084 
00085 #define THRBLOCXX_IF(tst, ExceptionClass, msg) \
00086    do \
00087    { \
00088       if (tst) \
00089       { \
00090          BLOCXX_THROW(ExceptionClass, (msg)); \
00091       } \
00092    } while (false)
00093 
00094 #define THRBLOCXX_ERRNO_IF(tst, ExceptionClass, msg) \
00095    do \
00096    { \
00097       if (tst) \
00098       { \
00099          BLOCXX_THROW_ERRNO_MSG(ExceptionClass, (msg)); \
00100       } \
00101    } while (false)
00102 
00103 #define ABORT_IF(tst, msg) THRBLOCXX_IF((tst), Secure::ProcessAbortException, (msg))
00104 
00105 #define ABORT_ERRNO_IF(tst, msg) \
00106   THRBLOCXX_ERRNO_IF((tst), Secure::ProcessAbortException, (msg))
00107 
00108 namespace
00109 {
00110 #if !defined(BLOCXX_HAVE_SETEUID) && defined(BLOCXX_HAVE_SETREUID)
00111 int seteuid(uid_t euid)
00112 {
00113    return (setreuid(-1, euid));
00114 }
00115 
00116 #endif
00117 
00118 #if !defined(BLOCXX_HAVE_SETEGID) && defined(BLOCXX_HAVE_SETRESGID)
00119 int setegid(uid_t egid)
00120 {
00121    return(setresgid(-1, egid, -1));
00122 }
00123 #endif
00124 
00125 } // end anonymous namespace
00126 
00127 namespace BLOCXX_NAMESPACE
00128 {
00129 namespace Secure
00130 {
00131    BLOCXX_DEFINE_EXCEPTION(ProcessAbort);
00132 
00133    // Original source: Item 1.3, _Secure Programming Cookbook for C and C++_, by
00134    // John Viega and Matt Messier. 
00135    // Original C code reformatted and modified for C++.
00136    // Some inspiration provided by uidswap.c from openssh-portable
00137    void dropPrivilegesPermanently(::uid_t newuid, ::gid_t newgid, EChildGroupAction extendedGroupAction)
00138    {
00139 #ifdef BLOCXX_WIN32
00140 #pragma message(Reminder "TODO: implement it for Win!")
00141 #else
00142       // Note: If any manipulation of privileges cannot be completed
00143       // successfully, it is safest to assume that the process is in an
00144       // unknown state and not allow it to continue (abort).
00145 
00146       if (newgid == ::gid_t(-1))
00147       {
00148          newgid = ::getgid();
00149       }
00150       ::gid_t oldegid = ::getegid();
00151       ::gid_t oldgid = ::getgid();
00152       if (newuid == ::uid_t(-1))
00153       {
00154          newuid = ::getuid();
00155       }
00156       ::uid_t oldeuid = ::geteuid();
00157       ::uid_t olduid = ::getuid();
00158 
00159       // If root privileges are to be dropped, be sure to pare down the
00160       // ancillary groups for the process before doing anything else because
00161       // the setgroups() system call requires root privileges.  Drop ancillary
00162       // groups regardless of whether privileges are being dropped temporarily
00163       // or permanently.
00164       if (oldeuid == 0)
00165       {
00166          struct passwd *newuser(NULL);
00167          if (extendedGroupAction == E_SOURCE_EXTENDED_GROUPS)
00168          {
00169             newuser = ::getpwuid(newuid);
00170          }
00171          if (newuser)
00172          {
00173             ::initgroups(newuser->pw_name, newgid);
00174          }
00175          else
00176          {
00177          ::setgroups(1, &newgid);
00178       }
00179       }
00180 
00181       if (newgid != oldegid)
00182       {
00183 #if defined(BLOCXX_HAVE_SETRESGID) && !defined(BLOCXX_BROKEN_SETRESGID)
00184          ABORT_ERRNO_IF(::setresgid(newgid, newgid, newgid) == -1, "drop_privileges [1]");
00185 #elif defined(BLOCXX_HAVE_SETREGID) && !defined(BLOCXX_BROKEN_SETREGID)
00186          ABORT_ERRNO_IF(::setregid(newgid, newgid) == -1, "drop_privileges [1]");
00187 #else
00188          ABORT_ERRNO_IF(::setegid(newgid) == -1, "drop_privileges [1]");
00189          ABORT_ERRNO_IF(::setgid(newgid) == -1, "drop_privileges [1.1]");
00190 #endif
00191       }
00192 
00193       if (newuid != oldeuid)
00194       {
00195 #if defined(BLOCXX_HAVE_SETRESUID) && !defined(BLOCXX_BROKEN_SETRESUID)
00196          ABORT_ERRNO_IF(::setresuid(newuid, newuid, newuid) == -1, "drop_privileges [2]");
00197 #elif defined(BLOCXX_HAVE_SETREUID) && !defined(BLOCXX_BROKEN_SETREUID)
00198          ABORT_ERRNO_IF(::setreuid(newuid, newuid) == -1, "drop_privileges [2]");
00199 #else
00200 #if !defined(BLOCXX_SETEUID_BREAKS_SETUID)
00201          ABORT_ERRNO_IF(::seteuid(newuid) == -1, "drop_privileges [2]");
00202 #endif
00203          ABORT_ERRNO_IF(::setuid(newuid) == -1, "drop_privileges [2.1]");
00204 #endif
00205       }
00206 
00207       // verify that the changes were successful
00208       // make sure gid drop was successful
00209       ABORT_IF(::getgid() != newgid || ::getegid() != newgid, "drop_privileges [3]");
00210 
00211       // make sure gid restoration fails
00212       ABORT_IF(
00213          newuid != 0 && newgid != oldegid &&
00214 #if defined(BLOCXX_HAVE_SETRESGID) && !defined(BLOCXX_BROKEN_SETRESGID)
00215          (::setresgid(oldegid, oldegid, oldegid) != -1 || ::setgid(oldgid) != -1),
00216 #elif defined(BLOCXX_HAVE_SETREGID) && !defined(BLOCXX_BROKEN_SETREGID)
00217          (::setregid(oldegid, oldegid) != -1 || ::setgid(oldgid) != -1),
00218 #else
00219          (::setegid(oldegid) != -1 || ::setgid(oldgid) != -1),
00220 #endif
00221          "drop_privileges [4]"
00222       );
00223 
00224       // make sure uid drop was successful
00225       ABORT_IF(::getuid() != newuid || ::geteuid() != newuid, "drop_privileges [5]");
00226 
00227       // make sure uid restoration fails
00228       ABORT_IF(
00229          newuid != 0 && newuid != oldeuid &&
00230 #if defined(BLOCXX_HAVE_SETRESUID) && !defined(BLOCXX_BROKEN_SETRESUID)
00231          (::setresuid(oldeuid, oldeuid, oldeuid) != -1 || ::setuid(olduid) != -1),
00232 #elif defined(BLOCXX_HAVE_SETREUID) && !defined(BLOCXX_BROKEN_SETREUID)
00233          (::setreuid(oldeuid, oldeuid) != -1 || ::setuid(olduid) != -1),
00234 #else
00235          (::seteuid(oldeuid) != -1 || ::setuid(olduid) != -1),
00236 #endif
00237          "drop_privileges [6]"
00238       );
00239 #endif
00240    }
00241 
00242 namespace
00243 {
00244 #ifdef AIX
00245    NonRecursiveMutex envMutex;
00246    String odmdir;
00247 
00248    char const default_odmdir[] = "ODMDIR=/etc/objrepos";
00249 
00250    String check_line(String const & line)
00251    {
00252       StringBuffer sb;
00253       char const * s;
00254       char c;
00255       for (s = line.c_str(); (c = *s) && !std::isspace(c); ++s)
00256       {
00257          switch (c)
00258          {
00259             case '\\':
00260                if (s[1] == '\0')
00261                {
00262                   // Unexpected format
00263                   return default_odmdir;
00264                }
00265                c = *++s;
00266                break;
00267             case '$':
00268             case '`':
00269             case '"':
00270             case '\'':
00271                // Unexpected format
00272                return default_odmdir;
00273             default:
00274                ;
00275          }
00276          sb += c;
00277       }
00278       if (c == '\0')
00279       {
00280          return sb.releaseString();
00281       }
00282       while (std::isspace(*s))
00283       {
00284          ++s;
00285       }
00286       if (*s == '#')
00287       {
00288          return sb.releaseString();
00289       }
00290       // Unexpected format
00291       return default_odmdir;
00292    }
00293 
00294    String setODMDIR()
00295    {
00296       String retval(default_odmdir);
00297       std::ifstream is("/etc/environment");
00298       while (is)
00299       {
00300          String s = String::getLine(is).trim();
00301          if (s.startsWith("ODMDIR="))
00302          {
00303             retval = check_line(s);
00304          }
00305       }
00306       return retval;
00307    }
00308 
00309    void addPlatformSpecificEnvVars(StringArray & environ)
00310    {
00311       NonRecursiveMutexLock lock(envMutex);
00312       if (odmdir.empty())
00313       {
00314          odmdir = setODMDIR();
00315       }
00316       environ.push_back(odmdir);
00317    }
00318 
00319 #else
00320 
00321    void addPlatformSpecificEnvVars(StringArray &absEnvironment)
00322    {
00323 #ifdef BLOCXX_WIN32
00324       char* const lpInheritedEnvironment = GetEnvironmentStrings();
00325       char* lpInheritedEnvIterator = lpInheritedEnvironment;
00326       if (lpInheritedEnvironment && *lpInheritedEnvironment && lpInheritedEnvironment[1])
00327       {
00328          for ( ; *lpInheritedEnvIterator; lpInheritedEnvIterator++)
00329          {
00330             absEnvironment.push_back( String( lpInheritedEnvIterator ) );
00331             lpInheritedEnvIterator += lstrlen(lpInheritedEnvIterator);
00332          }
00333          FreeEnvironmentStrings( (LPTCH)lpInheritedEnvironment );
00334       }
00335 #endif
00336    }
00337 
00338 #endif
00339 
00340    struct MinimalEnvironmentConstructor
00341    {
00342       static StringArray* create(int dummy)
00343       {
00344          AutoPtr<StringArray> retval(new StringArray);
00345          retval->push_back("IFS= \t\n");
00346          retval->push_back("PATH=" _PATH_STDPATH);
00347          char * tzstr = ::getenv("TZ");
00348          if (tzstr)
00349          {
00350             retval->push_back(String("TZ=") + tzstr);
00351          }
00352          addPlatformSpecificEnvVars(*retval);
00353          return retval.release();
00354       }
00355    };
00356 
00357    LazyGlobal<StringArray, int, MinimalEnvironmentConstructor> g_minimalEnvironment = BLOCXX_LAZY_GLOBAL_INIT(0);
00358 } // end unnamed namespace
00359 
00360    StringArray minimalEnvironment()
00361    {
00362       return g_minimalEnvironment;
00363    }
00364 
00365    void runAs(char const * username, EChildGroupAction extendedGroupAction)
00366    {
00367 #ifdef BLOCXX_WIN32
00368 #pragma message(Reminder "TODO: implement it for Win!")
00369 #else
00370       ABORT_IF(!username, "null user name");
00371       ABORT_IF(*username == '\0', "empty user name");
00372       ABORT_IF(::getuid() != 0 || ::geteuid() != 0, "non-root user calling runAs");
00373       errno = 0;
00374       struct passwd * pwent = ::getpwnam(username);
00375       // return value from getpwnam is a static, so don't free it.
00376       ABORT_ERRNO_IF(!pwent && errno != 0, Format("getpwnam(\"%1\") failed", username).c_str());
00377       ABORT_IF(!pwent, Format("user name (%1) not found", username).c_str());
00378       int rc = ::chdir("/");
00379       ABORT_ERRNO_IF(rc != 0, "chdir failed");
00380       Secure::dropPrivilegesPermanently(pwent->pw_uid, pwent->pw_gid, extendedGroupAction);
00381 #endif
00382    }
00383 
00384 } // namespace Secure
00385 } // namespace BLOCXX_NAMESPACE