blocxx
|
00001 /******************************************************************************* 00002 * Copyright (C) 2005, Vintela, 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 * Vintela, 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 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 // Conceptually, this class consists of two values: 00063 // - A resolved part, which can have path components added or removed 00064 // at the end, and 00065 // - an unresolved part, which can have path components added or removed 00066 // at the beginning. 00067 // 00068 class PartiallyResolvedPath 00069 { 00070 public: 00071 // PROMISE: Resolved part is base_dir and unresolved part is empty. 00072 // REQUIRE: base_dir is in canonical form. 00073 PartiallyResolvedPath(char const * base_dir); 00074 00075 // Prepends the components of path to the unresolved part of the path. 00076 // Note that path may be empty. 00077 // REQUIRE: path does not start with '/'. 00078 void multi_push_unresolved(char const * path); 00079 00080 // Discards the first component of the unresolved part. 00081 // REQUIRE: unresolved part nonempty. 00082 void pop_unresolved(); 00083 00084 // RETURNS: true iff the unresolved part is empty 00085 bool unresolved_empty() const; 00086 00087 // RETURNS: true iff the first component of the unresolved part is ".". 00088 bool unresolved_starts_with_curdir() const; 00089 00090 // RETURNS: true iff the first component of the unresolved part is "..". 00091 bool unresolved_starts_with_parent() const; 00092 00093 // RETURNS: true iff push_unresolved(path) has ever been called 00094 // with an empty unresolved part and path ending in '/'. 00095 bool dir_specified() const; 00096 00097 // Transfers the first component of the unresolved part to the end 00098 // of the resolved part. 00099 // REQUIRE: unresolved part is nonempty. 00100 void xfer_component(); 00101 00102 // Discards the last component of the resolved part. 00103 // If resolved part is "/", does nothing (parent of "/" is "/"). 00104 void pop_resolved(); 00105 00106 // Resets the resolved part ot "/". 00107 void reset_resolved(); 00108 00109 // RETURNS: the resolved part, as a String. 00110 String get_resolved() const; 00111 00112 // Calls lstat on the resolved part. 00113 void lstat_resolved(struct stat & st) const; 00114 00115 // REQUIRE: resolved part is not "/", and last component is a symbolic 00116 // link. 00117 // PROMISE: Reads the symbolic link and assigns it to path (including 00118 // a terminating '\0' character). 00119 void read_symlink(std::vector<char> & path); 00120 00121 private: 00122 00123 // INVARIANT: Holds an absolute path with no duplicate '/' chars, 00124 // no "." or ".." components, no component (except possibly the last) 00125 // that is a symlink, and no terminating '/' unless the whole path is 00126 // "/". 00127 mutable std::vector<char> m_resolved; 00128 00129 // INVARIANT: holds a relative path with no repeated or terminating '/' 00130 // chars, stored in reverse order. 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; // parent of "/" is "/" 00242 } 00243 BLOCXX_ASSERT(m_resolved.back() != '/'); 00244 while (m_resolved.back() != '/') 00245 { 00246 m_resolved.pop_back(); 00247 } 00248 // pop off path separator too, unless we are back to the root dir 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 // Note that if the link value is too big to fit into buf, but 00315 // there is no other error, then rv == buf.size(); in particular, 00316 // we do NOT get rv < 0 with errno == ENAMETOOLONG (this refers 00317 // to the input path, not the link value returned). 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 // This handles the case where there are no unresolved items in the path (only possible for '/') 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 ) // we need at least 3 symbols 00498 { 00499 wrapped_lstat("/", st); 00500 } 00501 else 00502 { 00503 sRootPath[3] = 0; // we're interesting only for len('X:/')=3 letters 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 } // anonymous namespace 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 } // end namespace BLOCXX_NAMESPACE 00549