00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00038 #include "blocxx/BLOCXX_config.h"
00039 #include "blocxx/PathSecurity.hpp"
00040 #include "blocxx/Assertion.hpp"
00041 #include "blocxx/FileSystem.hpp"
00042 #include "blocxx/Format.hpp"
00043 #include "blocxx/String.hpp"
00044 #include "blocxx/Logger.hpp"
00045 #include "blocxx/UserUtils.hpp"
00046 #ifdef BLOCXX_HAVE_UNISTD_H
00047 #include <unistd.h>
00048 #endif
00049 #include <vector>
00050
00051 namespace BLOCXX_NAMESPACE
00052 {
00053
00054 typedef Array<std::pair<String, EFileStatusReturn> > path_results_t;
00055
00056 namespace
00057 {
00058 using namespace FileSystem::Path;
00059
00060 unsigned const MAX_SYMBOLIC_LINKS = 100;
00061
00062
00063
00064
00065
00066
00067
00068 class PartiallyResolvedPath
00069 {
00070 public:
00071
00072
00073 PartiallyResolvedPath(char const * base_dir);
00074
00075
00076
00077
00078 void multi_push_unresolved(char const * path);
00079
00080
00081
00082 void pop_unresolved();
00083
00084
00085 bool unresolved_empty() const;
00086
00087
00088 bool unresolved_starts_with_curdir() const;
00089
00090
00091 bool unresolved_starts_with_parent() const;
00092
00093
00094
00095 bool dir_specified() const;
00096
00097
00098
00099
00100 void xfer_component();
00101
00102
00103
00104 void pop_resolved();
00105
00106
00107 void reset_resolved();
00108
00109
00110 String get_resolved() const;
00111
00112
00113 void lstat_resolved(struct stat & st) const;
00114
00115
00116
00117
00118
00119 void read_symlink(std::vector<char> & path);
00120
00121 private:
00122
00123
00124
00125
00126
00127 mutable std::vector<char> m_resolved;
00128
00129
00130
00131 std::vector<char> m_unresolved;
00132
00133 bool m_dir_specified;
00134 };
00135
00136 PartiallyResolvedPath::PartiallyResolvedPath(char const * base_dir)
00137 : m_resolved(base_dir, base_dir + std::strlen(base_dir)),
00138 m_unresolved(),
00139 m_dir_specified(false)
00140 {
00141 }
00142
00143 void PartiallyResolvedPath::multi_push_unresolved(char const * path)
00144 {
00145 BLOCXX_ASSERT(path && *path != '/');
00146 if (*path == '\0')
00147 {
00148 return;
00149 }
00150 char const * end = path;
00151 while (*end != '\0')
00152 {
00153 ++end;
00154 }
00155 if (end != path && *(end - 1) == '/')
00156 {
00157 m_dir_specified = true;
00158 }
00159 m_unresolved.push_back('/');
00160 bool last_separator = true;
00161 while (end != path)
00162 {
00163 char c = *--end;
00164 bool separator = (c == '/');
00165 if (!(separator && last_separator))
00166 {
00167 m_unresolved.push_back(c);
00168 }
00169 last_separator = separator;
00170 }
00171 }
00172
00173 void PartiallyResolvedPath::pop_unresolved()
00174 {
00175 BLOCXX_ASSERT(!m_unresolved.empty());
00176 while (!m_unresolved.empty() && m_unresolved.back() != '/')
00177 {
00178 m_unresolved.pop_back();
00179 }
00180 while (!m_unresolved.empty() && m_unresolved.back() == '/')
00181 {
00182 m_unresolved.pop_back();
00183 }
00184 }
00185
00186 inline bool PartiallyResolvedPath::unresolved_empty() const
00187 {
00188 return m_unresolved.empty();
00189 }
00190
00191 bool PartiallyResolvedPath::unresolved_starts_with_curdir() const
00192 {
00193 std::size_t n = m_unresolved.size();
00194 return (
00195 n > 0 && m_unresolved[n - 1] == '.' &&
00196 (n == 1 || m_unresolved[n - 2] == '/')
00197 );
00198 }
00199
00200 bool PartiallyResolvedPath::unresolved_starts_with_parent() const
00201 {
00202 std::size_t n = m_unresolved.size();
00203 return (
00204 n >= 2 && m_unresolved[n - 1] == '.' && m_unresolved[n - 2] == '.'
00205 && (n == 2 || m_unresolved[n - 3] == '/')
00206 );
00207 }
00208
00209 inline bool PartiallyResolvedPath::dir_specified() const
00210 {
00211 return m_dir_specified;
00212 }
00213
00214 void PartiallyResolvedPath::xfer_component()
00215 {
00216 BLOCXX_ASSERT(!m_unresolved.empty());
00217 std::size_t n = m_resolved.size();
00218 BLOCXX_ASSERT(n > 0 && (n == 1 || m_resolved[n - 1] != '/'));
00219 if (n > 1)
00220 {
00221 m_resolved.push_back('/');
00222 }
00223 char c;
00224 while (!m_unresolved.empty() && (c = m_unresolved.back()) != '/')
00225 {
00226 m_unresolved.pop_back();
00227 m_resolved.push_back(c);
00228 }
00229 while (!m_unresolved.empty() && m_unresolved.back() == '/')
00230 {
00231 m_unresolved.pop_back();
00232 }
00233 }
00234
00235 void PartiallyResolvedPath::pop_resolved()
00236 {
00237 std::size_t n = m_resolved.size();
00238 BLOCXX_ASSERT(n > 0 && m_resolved[0] == '/');
00239 if (n == 1)
00240 {
00241 return;
00242 }
00243 BLOCXX_ASSERT(m_resolved.back() != '/');
00244 while (m_resolved.back() != '/')
00245 {
00246 m_resolved.pop_back();
00247 }
00248
00249 if (m_resolved.size() > 1)
00250 {
00251 m_resolved.pop_back();
00252 }
00253 }
00254
00255 inline void PartiallyResolvedPath::reset_resolved()
00256 {
00257 std::vector<char>(1, '/').swap(m_resolved);
00258 }
00259
00260 class NullTerminate
00261 {
00262 std::vector<char> & m_buf;
00263 public:
00264 NullTerminate(std::vector<char> & buf)
00265 : m_buf(buf)
00266 {
00267 m_buf.push_back('\0');
00268 }
00269
00270 ~NullTerminate()
00271 {
00272 m_buf.pop_back();
00273 }
00274 };
00275
00276 inline String PartiallyResolvedPath::get_resolved() const
00277 {
00278 NullTerminate x(m_resolved);
00279 return String(&m_resolved[0]);
00280 }
00281
00282 void wrapped_lstat(char const * path, struct stat & st)
00283 {
00284 #ifdef BLOCXX_WIN32
00285 String tmp_path(path);
00286 if(path[1] == ':' && path[2] == 0)
00287 {
00288 tmp_path += "\\";
00289 }
00290 if (LSTAT(tmp_path.c_str(), &st) < 0)
00291 #else
00292 if (LSTAT(path, &st) < 0)
00293 #endif
00294 {
00295 BLOCXX_THROW_ERRNO_MSG(FileSystemException, path);
00296 }
00297 }
00298
00299 void PartiallyResolvedPath::lstat_resolved(struct stat & st) const
00300 {
00301 NullTerminate x(m_resolved);
00302 wrapped_lstat(&m_resolved[0], st);
00303 }
00304
00305 void PartiallyResolvedPath::read_symlink(std::vector<char> & path)
00306 {
00307 BLOCXX_ASSERT(READLINK_ALLOWED);
00308 NullTerminate x(m_resolved);
00309 std::vector<char> buf(MAXPATHLEN + 1);
00310 while (true)
00311 {
00312 char const * symlink_path = &m_resolved[0];
00313 int rv = READLINK(symlink_path, &buf[0], buf.size());
00314
00315
00316
00317
00318 if (rv < 0)
00319 {
00320 BLOCXX_THROW_ERRNO_MSG(FileSystemException, symlink_path);
00321 }
00322 else if (static_cast<unsigned>(rv) == buf.size())
00323 {
00324 buf.resize(2 * buf.size());
00325 }
00326 else
00327 {
00328 path.swap(buf);
00329 return;
00330 }
00331 }
00332 }
00333
00334 char const * strip_leading_slashes(char const * path)
00335 {
00336 while (*path == '/')
00337 {
00338 ++path;
00339 }
00340 return path;
00341 }
00342
00343 void logFileStatus(path_results_t const & results, const uid_t uid)
00344 {
00345 Logger logger("blocxx.PathSecurity");
00346 for (path_results_t::const_iterator li = results.begin(); li != results.end(); ++li)
00347 {
00348 switch (li->second)
00349 {
00350 case E_FILE_BAD_OWNER:
00351 {
00352 String hpux;
00353
00354 bool successful(false);
00355 String userName = UserUtils::getUserName(uid, successful);
00356 if (!successful)
00357 {
00358 userName = "the proper user";
00359 }
00360 #if defined(BLOCXX_HPUX) || defined(BLOCXX_AIX)
00361 else
00362 {
00363 if (uid == 0)
00364 {
00365 hpux = " (or bin)";
00366 }
00367 }
00368 #endif
00369
00370 BLOCXX_LOG_ERROR(logger, Format("%1 was insecure. It was not owned by %2%3.", li->first, userName, hpux));
00371 break;
00372 }
00373 case E_FILE_BAD_OTHER:
00374 {
00375
00376 BLOCXX_LOG_ERROR(logger, Format("%1 was owned by the proper user, but was not a symlink and was either"
00377 " world (or non-root group) writable or did not have the sticky bit set on the directory.", li->first));
00378 break;
00379 }
00380 default:
00381 break;
00382 }
00383 }
00384 }
00385
00386 std::pair<ESecurity, String>
00387 path_security(char const * base_dir, char const * rel_path, uid_t uid, bool bdsecure)
00388 {
00389 BLOCXX_ASSERT(base_dir[0] == '/');
00390 BLOCXX_ASSERT(
00391 base_dir[1] == '\0' || base_dir[std::strlen(base_dir) - 1] != '/');
00392 BLOCXX_ASSERT(rel_path[0] != '/');
00393
00394 struct stat st;
00395
00396 #ifndef BLOCXX_WIN32
00397 PartiallyResolvedPath prp(base_dir);
00398 #else
00399 PartiallyResolvedPath prp("");
00400 if (rel_path[1] != ':' && base_dir[1] == ':')
00401 {
00402 prp = base_dir;
00403 }
00404 #endif
00405 ESecurity status_if_secure = E_SECURE_DIR;
00406 unsigned num_symbolic_links = 0;
00407 EFileStatusReturn file_status(E_FILE_BAD_OTHER);
00408 path_results_t results;
00409
00410 prp.multi_push_unresolved(rel_path);
00411
00412 if( prp.unresolved_empty() )
00413 {
00414 prp.lstat_resolved(st);
00415 file_status = getFileStatus(st, uid, true, rel_path);
00416 }
00417 while (!prp.unresolved_empty())
00418 {
00419 if (prp.unresolved_starts_with_curdir())
00420 {
00421 prp.pop_unresolved();
00422 }
00423 else if (prp.unresolved_starts_with_parent())
00424 {
00425 prp.pop_unresolved();
00426 prp.pop_resolved();
00427 }
00428 else
00429 {
00430 prp.xfer_component();
00431 prp.lstat_resolved(st);
00432 file_status = getFileStatus(st, uid, prp.unresolved_empty(), prp.get_resolved());
00433
00434 if (file_status != E_FILE_OK)
00435 {
00436 results.push_back(std::make_pair(prp.get_resolved(), file_status));
00437 }
00438
00439 if (S_ISREG(st.st_mode))
00440 {
00441 status_if_secure = E_SECURE_FILE;
00442 if (!prp.unresolved_empty() || prp.dir_specified())
00443 {
00444 BLOCXX_THROW_ERRNO_MSG1(
00445 FileSystemException, prp.get_resolved(), ENOTDIR);
00446 }
00447 }
00448 else if (S_ISLNK(st.st_mode))
00449 {
00450 if (++num_symbolic_links > MAX_SYMBOLIC_LINKS)
00451 {
00452 BLOCXX_THROW_ERRNO_MSG1(
00453 FileSystemException, prp.get_resolved(), ELOOP);
00454 }
00455 std::vector<char> slpath_vec;
00456 prp.read_symlink(slpath_vec);
00457 char const * slpath = &slpath_vec[0];
00458 if (slpath[0] == '/')
00459 {
00460 prp.reset_resolved();
00461 slpath = strip_leading_slashes(slpath);
00462 }
00463 else
00464 {
00465 prp.pop_resolved();
00466 }
00467 prp.multi_push_unresolved(slpath);
00468 }
00469 else if (!S_ISDIR(st.st_mode))
00470 {
00471 String msg = prp.get_resolved() +
00472 " is not a directory, symbolic link, nor regular file";
00473 BLOCXX_THROW(FileSystemException, msg.c_str());
00474 }
00475 }
00476 }
00477
00478 ESecurity sec = (bdsecure && file_status == E_FILE_OK) ? status_if_secure : E_INSECURE;
00479 logFileStatus(results, uid);
00480 return std::make_pair(sec, prp.get_resolved());
00481 }
00482
00483 std::pair<ESecurity, String> path_security(char const * path, UserId uid)
00484 {
00485 if(!isPathAbsolute(path))
00486 {
00487 BLOCXX_THROW(FileSystemException,
00488 Format("%1 is not an absolute path", path).c_str());
00489 }
00490 char const * relpath = strip_leading_slashes(path);
00491 struct stat st;
00492 #ifndef BLOCXX_WIN32
00493 char sRootPath[] = "/";
00494 wrapped_lstat("/", st);
00495 #else
00496 char sRootPath[MAX_PATH];
00497 if (::GetWindowsDirectory(sRootPath, MAX_PATH) < 3 )
00498 {
00499 wrapped_lstat("/", st);
00500 }
00501 else
00502 {
00503 sRootPath[3] = 0;
00504 wrapped_lstat(sRootPath, st);
00505 }
00506 #endif
00507 EFileStatusReturn file_status = getFileStatus(st, uid, *relpath == '\0', path);
00508
00509 path_results_t results;
00510 if (file_status != E_FILE_OK)
00511 {
00512 results.push_back(std::make_pair(String(path), file_status));
00513 logFileStatus(results, uid);
00514 }
00515
00516 return path_security(sRootPath, relpath, uid, (file_status == E_FILE_OK));
00517 }
00518
00519 }
00520
00521 std::pair<ESecurity, String>
00522 FileSystem::Path::security(String const & path, UserId uid)
00523 {
00524 String abspath = isPathAbsolute(path) ? path : getCurrentWorkingDirectory() + BLOCXX_FILENAME_SEPARATOR + path;
00525 return path_security(abspath.c_str(), uid);
00526 }
00527
00528 std::pair<ESecurity, String>
00529 FileSystem::Path::security(
00530 String const & base_dir, String const & rel_path, UserId uid)
00531 {
00532 return path_security(base_dir.c_str(), rel_path.c_str(), uid, true);
00533 }
00534
00535 std::pair<ESecurity, String>
00536 FileSystem::Path::security(String const & path)
00537 {
00538 return security(path, ::geteuid());
00539 }
00540
00541 std::pair<ESecurity, String>
00542 FileSystem::Path::security(
00543 String const & base_dir, String const & rel_path)
00544 {
00545 return security(base_dir, rel_path, ::geteuid());
00546 }
00547
00548 }
00549