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 
357  LazyGlobal<StringArray, int, MinimalEnvironmentConstructor> g_minimalEnvironment = BLOCXX_LAZY_GLOBAL_INIT(0);
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