blocxx
Secure.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2 * Copyright (C) 2005, Quest Software, Inc. All rights reserved.
3 * Copyright (C) 2006, Novell, Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of
14 * Quest Software, Inc.,
15 * nor Novell, Inc.,
16 * nor the names of its contributors or employees may be used to
17 * endorse or promote products derived from this software without
18 * specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 *******************************************************************************/
32 
33 
34 #include "blocxx/BLOCXX_config.h"
35 #include "blocxx/Array.hpp"
36 #include "blocxx/Secure.hpp"
37 #include "blocxx/FileSystem.hpp"
38 #include "blocxx/String.hpp"
39 #include "blocxx/Paths.hpp"
40 #include "blocxx/Format.hpp"
41 #include "blocxx/LazyGlobal.hpp"
42 #ifdef BLOCXX_HAVE_DIRENT_H
43 #include <dirent.h>
44 #endif
45 #include <fcntl.h>
46 #ifndef BLOCXX_WIN32
47 #include <grp.h>
48 #endif
49 #include <limits.h>
50 #ifdef BLOCXX_HAVE_PWD_H
51 #include <pwd.h>
52 #endif
53 #ifdef BLOCXX_HAVE_SYS_PARAM_H
54 #include <sys/param.h>
55 #endif
56 #include <sys/types.h>
57 #include <sys/stat.h>
58 #ifdef BLOCXX_HAVE_UNISTD_H
59 #include <unistd.h>
60 #endif
61 #include <cstdlib>
62 #include <cstdio>
63 #include <cerrno>
64 #include <vector>
65 #include <algorithm>
66 
67 #ifdef AIX
68 #include "blocxx/StringBuffer.hpp"
71 #include <fstream>
72 #include <cctype>
73 #endif
74 
75 #if defined(BLOCXX_NO_SETRESGID_PROTO) && defined(BLOCXX_HAVE_SETRESGID)
76 extern "C" { int setresgid(gid_t rgid, gid_t egid, gid_t sgid); }
77 #endif
78 
79 #if defined(BLOCXX_NO_SETRESUID_PROTO) && defined(BLOCXX_HAVE_SETRESUID)
80 extern "C" { int setresuid(uid_t ruid, uid_t euid, uid_t suid); }
81 #endif
82 
83 using namespace blocxx;
84 
85 #define THRBLOCXX_IF(tst, ExceptionClass, msg) \
86  do \
87  { \
88  if (tst) \
89  { \
90  BLOCXX_THROW(ExceptionClass, (msg)); \
91  } \
92  } while (false)
93 
94 #define THRBLOCXX_ERRNO_IF(tst, ExceptionClass, msg) \
95  do \
96  { \
97  if (tst) \
98  { \
99  BLOCXX_THROW_ERRNO_MSG(ExceptionClass, (msg)); \
100  } \
101  } while (false)
102 
103 #define ABORT_IF(tst, msg) THRBLOCXX_IF((tst), Secure::ProcessAbortException, (msg))
104 
105 #define ABORT_ERRNO_IF(tst, msg) \
106  THRBLOCXX_ERRNO_IF((tst), Secure::ProcessAbortException, (msg))
107 
108 namespace
109 {
110 #if !defined(BLOCXX_HAVE_SETEUID) && defined(BLOCXX_HAVE_SETREUID)
111 int seteuid(uid_t euid)
112 {
113  return (setreuid(-1, euid));
114 }
115 
116 #endif
117 
118 #if !defined(BLOCXX_HAVE_SETEGID) && defined(BLOCXX_HAVE_SETRESGID)
119 int setegid(uid_t egid)
120 {
121  return(setresgid(-1, egid, -1));
122 }
123 #endif
124 
125 } // end anonymous namespace
126 
127 namespace BLOCXX_NAMESPACE
128 {
129 namespace Secure
130 {
131  BLOCXX_DEFINE_EXCEPTION(ProcessAbort);
132 
133  // Original source: Item 1.3, _Secure Programming Cookbook for C and C++_, by
134  // John Viega and Matt Messier.
135  // Original C code reformatted and modified for C++.
136  // Some inspiration provided by uidswap.c from openssh-portable
137  void dropPrivilegesPermanently(::uid_t newuid, ::gid_t newgid, EChildGroupAction extendedGroupAction)
138  {
139 #ifdef BLOCXX_WIN32
140 #pragma message(Reminder "TODO: implement it for Win!")
141 #else
142  // Note: If any manipulation of privileges cannot be completed
143  // successfully, it is safest to assume that the process is in an
144  // unknown state and not allow it to continue (abort).
145 
146  if (newgid == ::gid_t(-1))
147  {
148  newgid = ::getgid();
149  }
150  ::gid_t oldegid = ::getegid();
151  ::gid_t oldgid = ::getgid();
152  if (newuid == ::uid_t(-1))
153  {
154  newuid = ::getuid();
155  }
156  ::uid_t oldeuid = ::geteuid();
157  ::uid_t olduid = ::getuid();
158 
159  // If root privileges are to be dropped, be sure to pare down the
160  // ancillary groups for the process before doing anything else because
161  // the setgroups() system call requires root privileges. Drop ancillary
162  // groups regardless of whether privileges are being dropped temporarily
163  // or permanently.
164  if (oldeuid == 0)
165  {
166  struct passwd *newuser(NULL);
167  if (extendedGroupAction == E_SOURCE_EXTENDED_GROUPS)
168  {
169  newuser = ::getpwuid(newuid);
170  }
171  if (newuser)
172  {
173  ::initgroups(newuser->pw_name, newgid);
174  }
175  else
176  {
177  ::setgroups(1, &newgid);
178  }
179  }
180 
181  if (newgid != oldegid)
182  {
183 #if defined(BLOCXX_HAVE_SETRESGID) && !defined(BLOCXX_BROKEN_SETRESGID)
184  ABORT_ERRNO_IF(::setresgid(newgid, newgid, newgid) == -1, "drop_privileges [1]");
185 #elif defined(BLOCXX_HAVE_SETREGID) && !defined(BLOCXX_BROKEN_SETREGID)
186  ABORT_ERRNO_IF(::setregid(newgid, newgid) == -1, "drop_privileges [1]");
187 #else
188  ABORT_ERRNO_IF(::setegid(newgid) == -1, "drop_privileges [1]");
189  ABORT_ERRNO_IF(::setgid(newgid) == -1, "drop_privileges [1.1]");
190 #endif
191  }
192 
193  if (newuid != oldeuid)
194  {
195 #if defined(BLOCXX_HAVE_SETRESUID) && !defined(BLOCXX_BROKEN_SETRESUID)
196  ABORT_ERRNO_IF(::setresuid(newuid, newuid, newuid) == -1, "drop_privileges [2]");
197 #elif defined(BLOCXX_HAVE_SETREUID) && !defined(BLOCXX_BROKEN_SETREUID)
198  ABORT_ERRNO_IF(::setreuid(newuid, newuid) == -1, "drop_privileges [2]");
199 #else
200 #if !defined(BLOCXX_SETEUID_BREAKS_SETUID)
201  ABORT_ERRNO_IF(::seteuid(newuid) == -1, "drop_privileges [2]");
202 #endif
203  ABORT_ERRNO_IF(::setuid(newuid) == -1, "drop_privileges [2.1]");
204 #endif
205  }
206 
207  // verify that the changes were successful
208  // make sure gid drop was successful
209  ABORT_IF(::getgid() != newgid || ::getegid() != newgid, "drop_privileges [3]");
210 
211  // make sure gid restoration fails
212  ABORT_IF(
213  newuid != 0 && newgid != oldegid &&
214 #if defined(BLOCXX_HAVE_SETRESGID) && !defined(BLOCXX_BROKEN_SETRESGID)
215  (::setresgid(oldegid, oldegid, oldegid) != -1 || ::setgid(oldgid) != -1),
216 #elif defined(BLOCXX_HAVE_SETREGID) && !defined(BLOCXX_BROKEN_SETREGID)
217  (::setregid(oldegid, oldegid) != -1 || ::setgid(oldgid) != -1),
218 #else
219  (::setegid(oldegid) != -1 || ::setgid(oldgid) != -1),
220 #endif
221  "drop_privileges [4]"
222  );
223 
224  // make sure uid drop was successful
225  ABORT_IF(::getuid() != newuid || ::geteuid() != newuid, "drop_privileges [5]");
226 
227  // make sure uid restoration fails
228  ABORT_IF(
229  newuid != 0 && newuid != oldeuid &&
230 #if defined(BLOCXX_HAVE_SETRESUID) && !defined(BLOCXX_BROKEN_SETRESUID)
231  (::setresuid(oldeuid, oldeuid, oldeuid) != -1 || ::setuid(olduid) != -1),
232 #elif defined(BLOCXX_HAVE_SETREUID) && !defined(BLOCXX_BROKEN_SETREUID)
233  (::setreuid(oldeuid, oldeuid) != -1 || ::setuid(olduid) != -1),
234 #else
235  (::seteuid(oldeuid) != -1 || ::setuid(olduid) != -1),
236 #endif
237  "drop_privileges [6]"
238  );
239 #endif
240  }
241 
242 namespace
243 {
244 #ifdef AIX
245  NonRecursiveMutex envMutex;
246  String odmdir;
247 
248  char const default_odmdir[] = "ODMDIR=/etc/objrepos";
249 
250  String check_line(String const & line)
251  {
252  StringBuffer sb;
253  char const * s;
254  char c;
255  for (s = line.c_str(); (c = *s) && !std::isspace(c); ++s)
256  {
257  switch (c)
258  {
259  case '\\':
260  if (s[1] == '\0')
261  {
262  // Unexpected format
263  return default_odmdir;
264  }
265  c = *++s;
266  break;
267  case '$':
268  case '`':
269  case '"':
270  case '\'':
271  // Unexpected format
272  return default_odmdir;
273  default:
274  ;
275  }
276  sb += c;
277  }
278  if (c == '\0')
279  {
280  return sb.releaseString();
281  }
282  while (std::isspace(*s))
283  {
284  ++s;
285  }
286  if (*s == '#')
287  {
288  return sb.releaseString();
289  }
290  // Unexpected format
291  return default_odmdir;
292  }
293 
294  String setODMDIR()
295  {
296  String retval(default_odmdir);
297  std::ifstream is("/etc/environment");
298  while (is)
299  {
300  String s = String::getLine(is).trim();
301  if (s.startsWith("ODMDIR="))
302  {
303  retval = check_line(s);
304  }
305  }
306  return retval;
307  }
308 
309  void addPlatformSpecificEnvVars(StringArray & environ)
310  {
311  NonRecursiveMutexLock lock(envMutex);
312  if (odmdir.empty())
313  {
314  odmdir = setODMDIR();
315  }
316  environ.push_back(odmdir);
317  }
318 
319 #else
320 
321  void addPlatformSpecificEnvVars(StringArray &absEnvironment)
322  {
323 #ifdef BLOCXX_WIN32
324  char* const lpInheritedEnvironment = GetEnvironmentStrings();
325  char* lpInheritedEnvIterator = lpInheritedEnvironment;
326  if (lpInheritedEnvironment && *lpInheritedEnvironment && lpInheritedEnvironment[1])
327  {
328  for ( ; *lpInheritedEnvIterator; lpInheritedEnvIterator++)
329  {
330  absEnvironment.push_back( String( lpInheritedEnvIterator ) );
331  lpInheritedEnvIterator += lstrlen(lpInheritedEnvIterator);
332  }
333  FreeEnvironmentStrings( (LPTCH)lpInheritedEnvironment );
334  }
335 #endif
336  }
337 
338 #endif
339 
340  struct MinimalEnvironmentConstructor
341  {
342  static StringArray* create(int dummy)
343  {
344  AutoPtr<StringArray> retval(new StringArray);
345  retval->push_back("IFS= \t\n");
346  retval->push_back("PATH=" _PATH_STDPATH);
347  char * tzstr = ::getenv("TZ");
348  if (tzstr)
349  {
350  retval->push_back(String("TZ=") + tzstr);
351  }
352  addPlatformSpecificEnvVars(*retval);
353  return retval.release();
354  }
355  };
356 
358 } // end unnamed namespace
359 
361  {
362  return g_minimalEnvironment;
363  }
364 
365  void runAs(char const * username, EChildGroupAction extendedGroupAction)
366  {
367 #ifdef BLOCXX_WIN32
368 #pragma message(Reminder "TODO: implement it for Win!")
369 #else
370  ABORT_IF(!username, "null user name");
371  ABORT_IF(*username == '\0', "empty user name");
372  ABORT_IF(::getuid() != 0 || ::geteuid() != 0, "non-root user calling runAs");
373  errno = 0;
374  struct passwd * pwent = ::getpwnam(username);
375  // return value from getpwnam is a static, so don't free it.
376  ABORT_ERRNO_IF(!pwent && errno != 0, Format("getpwnam(\"%1\") failed", username).c_str());
377  ABORT_IF(!pwent, Format("user name (%1) not found", username).c_str());
378  int rc = ::chdir("/");
379  ABORT_ERRNO_IF(rc != 0, "chdir failed");
380  Secure::dropPrivilegesPermanently(pwent->pw_uid, pwent->pw_gid, extendedGroupAction);
381 #endif
382  }
383 
384 } // namespace Secure
385 } // namespace BLOCXX_NAMESPACE
Taken from RFC 1321.
BLOCXX_IMPORT char ** environ
bool empty() const
Definition: String.hpp:237
void dropPrivilegesPermanently(::uid_t newuid, ::gid_t newgid, EChildGroupAction extendedGroupAction)
Definition: Secure.cpp:137
This String class is an abstract data type that represents as NULL terminated string of characters...
Definition: String.hpp:66
const char * c_str() const
Definition: String.cpp:905
#define ABORT_ERRNO_IF(tst, msg)
Definition: Secure.cpp:105
bool startsWith(const char *arg, EIgnoreCaseFlag ignoreCase=E_CASE_SENSITIVE) const
Determine if this String object starts with a given substring.
Definition: String.cpp:672
StringArray minimalEnvironment()
Definition: Secure.cpp:360
The AutoPtr class provides a simple class for smart pointers to single objects (for multiple...
Definition: AutoPtr.hpp:62
String & trim()
Strip all leading and trailing space characters (as defined by the C function isspace()) from this St...
Definition: String.cpp:790
void push_back(const T &x)
Append an element to the end of the Array.
Definition: ArrayImpl.hpp:251
void runAs(char const *username, EChildGroupAction extendedGroupAction)
Look up user ID and group ID for username in password file, chdir to "/", then drop privileges and ru...
Definition: Secure.cpp:365
#define BLOCXX_DEFINE_EXCEPTION(NAME)
Define a new exception class named <NAME>Exception that derives from Exception.
Definition: Exception.hpp:439
This class can be used to store a global variable that is lazily initialized in a thread safe manner...
Definition: LazyGlobal.hpp:114
#define BLOCXX_LAZY_GLOBAL_INIT(...)
Statically initialize a LazyGlobal instance.
Definition: LazyGlobal.hpp:188
Note that descriptions of what exceptions may be thrown assumes that object is used correctly...
Note that descriptions of what exceptions may be thrown assumes that object is used correctly...
X * release()
Release ownership of the underlying pointer.
Definition: AutoPtr.hpp:159
#define ABORT_IF(tst, msg)
Definition: Secure.cpp:103
#define _PATH_STDPATH
Definition: Paths.hpp:61