| File: | home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-private/tmpxsedttj5/../../../src/lib/util/filesystem.cc |
| Warning: | line 270, column 33 The parameter must not be null |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | // Copyright (C) 2021-2026 Internet Systems Consortium, Inc. ("ISC") | |||
| 2 | // | |||
| 3 | // This Source Code Form is subject to the terms of the Mozilla Public | |||
| 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this | |||
| 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. | |||
| 6 | ||||
| 7 | #include <config.h> | |||
| 8 | ||||
| 9 | #include <exceptions/exceptions.h> | |||
| 10 | #include <util/filesystem.h> | |||
| 11 | #include <util/str.h> | |||
| 12 | ||||
| 13 | #include <cstdio> | |||
| 14 | #include <cstdlib> | |||
| 15 | #include <fstream> | |||
| 16 | #include <string> | |||
| 17 | #include <iostream> | |||
| 18 | ||||
| 19 | #include <dirent.h> | |||
| 20 | #include <fcntl.h> | |||
| 21 | #include <unistd.h> | |||
| 22 | ||||
| 23 | using namespace isc; | |||
| 24 | using namespace isc::util::str; | |||
| 25 | using namespace std; | |||
| 26 | ||||
| 27 | namespace isc { | |||
| 28 | namespace util { | |||
| 29 | namespace file { | |||
| 30 | ||||
| 31 | ||||
| 32 | string | |||
| 33 | getContent(string const& file_name) { | |||
| 34 | if (!exists(file_name)) { | |||
| 35 | isc_throw(BadValue, "Expected a file at path '" << file_name << "'")do { std::ostringstream oss__; oss__ << "Expected a file at path '" << file_name << "'"; throw BadValue("../../../src/lib/util/filesystem.cc" , 35, oss__.str().c_str()); } while (1); | |||
| 36 | } | |||
| 37 | if (!isFile(file_name)) { | |||
| 38 | isc_throw(BadValue, "Expected '" << file_name << "' to be a regular file")do { std::ostringstream oss__; oss__ << "Expected '" << file_name << "' to be a regular file"; throw BadValue( "../../../src/lib/util/filesystem.cc", 38, oss__.str().c_str( )); } while (1); | |||
| 39 | } | |||
| 40 | ifstream file(file_name, ios::in); | |||
| 41 | if (!file.is_open()) { | |||
| 42 | isc_throw(BadValue, "Cannot open '" << file_name)do { std::ostringstream oss__; oss__ << "Cannot open '" << file_name; throw BadValue("../../../src/lib/util/filesystem.cc" , 42, oss__.str().c_str()); } while (1); | |||
| 43 | } | |||
| 44 | string content; | |||
| 45 | getline(file, content); | |||
| 46 | return (content); | |||
| 47 | } | |||
| 48 | ||||
| 49 | bool | |||
| 50 | exists(string const& path) { | |||
| 51 | struct stat statbuf; | |||
| 52 | return (::stat(path.c_str(), &statbuf) == 0); | |||
| 53 | } | |||
| 54 | ||||
| 55 | mode_t | |||
| 56 | getPermissions(const std::string path) { | |||
| 57 | struct stat statbuf; | |||
| 58 | if (::stat(path.c_str(), &statbuf) < 0) { | |||
| 59 | return (0); | |||
| 60 | } | |||
| 61 | ||||
| 62 | return (statbuf.st_mode & (S_IRWXU(0400|0200|0100) | S_IRWXG((0400|0200|0100) >> 3) | S_IRWXO(((0400|0200|0100) >> 3) >> 3))); | |||
| 63 | } | |||
| 64 | ||||
| 65 | bool | |||
| 66 | hasPermissions(const std::string path, const mode_t& permissions) { | |||
| 67 | return (getPermissions(path) == permissions); | |||
| 68 | } | |||
| 69 | ||||
| 70 | bool | |||
| 71 | isDir(string const& path) { | |||
| 72 | struct stat statbuf; | |||
| 73 | if (::stat(path.c_str(), &statbuf) < 0) { | |||
| 74 | return (false); | |||
| 75 | } | |||
| 76 | return ((statbuf.st_mode & S_IFMT0170000) == S_IFDIR0040000); | |||
| 77 | } | |||
| 78 | ||||
| 79 | bool | |||
| 80 | isFile(string const& path) { | |||
| 81 | struct stat statbuf; | |||
| 82 | if (::stat(path.c_str(), &statbuf) < 0) { | |||
| 83 | return (false); | |||
| 84 | } | |||
| 85 | return ((statbuf.st_mode & S_IFMT0170000) == S_IFREG0100000); | |||
| 86 | } | |||
| 87 | ||||
| 88 | bool | |||
| 89 | isSocket(string const& path) { | |||
| 90 | struct stat statbuf; | |||
| 91 | if (::stat(path.c_str(), &statbuf) < 0) { | |||
| 92 | return (false); | |||
| 93 | } | |||
| 94 | return ((statbuf.st_mode & S_IFMT0170000) == S_IFSOCK0140000); | |||
| 95 | } | |||
| 96 | ||||
| 97 | void | |||
| 98 | setUmask() { | |||
| 99 | // No group write and no other access. | |||
| 100 | mode_t mask(S_IWGRP(0200 >> 3) | S_IRWXO(((0400|0200|0100) >> 3) >> 3)); | |||
| 101 | mode_t orig = umask(mask); | |||
| 102 | // Handle the case where the original umask was already more restrictive. | |||
| 103 | if ((orig | mask) != mask) { | |||
| 104 | static_cast<void>(umask(orig | mask)); | |||
| 105 | } | |||
| 106 | } | |||
| 107 | ||||
| 108 | RelaxUmask::RelaxUmask() : orig_umask_(umask(S_IRWXO(((0400|0200|0100) >> 3) >> 3))) { | |||
| 109 | } | |||
| 110 | ||||
| 111 | RelaxUmask::~RelaxUmask() { | |||
| 112 | static_cast<void>(umask(orig_umask_)); | |||
| 113 | } | |||
| 114 | ||||
| 115 | bool amRunningAsRoot() { | |||
| 116 | return (getuid() == 0 || geteuid() == 0); | |||
| 117 | } | |||
| 118 | ||||
| 119 | Path::Path(string const& full_name) { | |||
| 120 | dir_present_ = false; | |||
| 121 | if (!full_name.empty()) { | |||
| 122 | // Find the directory. | |||
| 123 | size_t last_slash = full_name.find_last_of('/'); | |||
| 124 | if (last_slash != string::npos) { | |||
| 125 | // Found a directory so note the fact. | |||
| 126 | dir_present_ = true; | |||
| 127 | ||||
| 128 | // Found the last slash, so extract directory component and | |||
| 129 | // set where the scan for the last_dot should terminate. | |||
| 130 | parent_path_ = full_name.substr(0, last_slash); | |||
| 131 | if (last_slash == full_name.size()) { | |||
| 132 | // The entire string was a directory, so exit and don't | |||
| 133 | // do any more searching. | |||
| 134 | return; | |||
| 135 | } | |||
| 136 | } | |||
| 137 | ||||
| 138 | // Now search backwards for the last ".". | |||
| 139 | size_t last_dot = full_name.find_last_of('.'); | |||
| 140 | if ((last_dot == string::npos) || (dir_present_ && (last_dot < last_slash))) { | |||
| 141 | // Last "." either not found or it occurs to the left of the last | |||
| 142 | // slash if a directory was present (so it is part of a directory | |||
| 143 | // name). In this case, the remainder of the string after the slash | |||
| 144 | // is the name part. | |||
| 145 | stem_ = full_name.substr(last_slash + 1); | |||
| 146 | return; | |||
| 147 | } | |||
| 148 | ||||
| 149 | // Did find a valid dot, so it and everything to the right is the | |||
| 150 | // extension... | |||
| 151 | extension_ = full_name.substr(last_dot); | |||
| 152 | ||||
| 153 | // ... and the name of the file is everything in between. | |||
| 154 | if ((last_dot - last_slash) > 1) { | |||
| 155 | stem_ = full_name.substr(last_slash + 1, last_dot - last_slash - 1); | |||
| 156 | } | |||
| 157 | } | |||
| 158 | } | |||
| 159 | ||||
| 160 | string | |||
| 161 | Path::str() const { | |||
| 162 | return (parent_path_ + (dir_present_ ? "/" : "") + stem_ + extension_); | |||
| 163 | } | |||
| 164 | ||||
| 165 | string | |||
| 166 | Path::parentPath() const { | |||
| 167 | return (parent_path_); | |||
| 168 | } | |||
| 169 | ||||
| 170 | string | |||
| 171 | Path::parentDirectory() const { | |||
| 172 | return (parent_path_ + (dir_present_ ? "/" : "")); | |||
| 173 | } | |||
| 174 | ||||
| 175 | string | |||
| 176 | Path::stem() const { | |||
| 177 | return (stem_); | |||
| 178 | } | |||
| 179 | ||||
| 180 | string | |||
| 181 | Path::extension() const { | |||
| 182 | return (extension_); | |||
| 183 | } | |||
| 184 | ||||
| 185 | string | |||
| 186 | Path::filename() const { | |||
| 187 | return (stem_ + extension_); | |||
| 188 | } | |||
| 189 | ||||
| 190 | Path& | |||
| 191 | Path::replaceExtension(string const& replacement) { | |||
| 192 | string const trimmed_replacement(trim(replacement)); | |||
| 193 | if (trimmed_replacement.empty()) { | |||
| 194 | extension_ = string(); | |||
| 195 | } else { | |||
| 196 | size_t const last_dot(trimmed_replacement.find_last_of('.')); | |||
| 197 | if (last_dot == string::npos) { | |||
| 198 | extension_ = "." + trimmed_replacement; | |||
| 199 | } else { | |||
| 200 | extension_ = trimmed_replacement.substr(last_dot); | |||
| 201 | } | |||
| 202 | } | |||
| 203 | return (*this); | |||
| 204 | } | |||
| 205 | ||||
| 206 | Path& | |||
| 207 | Path::replaceParentPath(string const& replacement) { | |||
| 208 | string const trimmed_replacement(trim(replacement)); | |||
| 209 | dir_present_ = (trimmed_replacement.find_last_of('/') != string::npos); | |||
| 210 | if (trimmed_replacement.empty() || (trimmed_replacement == "/")) { | |||
| 211 | parent_path_ = string(); | |||
| 212 | } else if (trimmed_replacement.at(trimmed_replacement.size() - 1) == '/') { | |||
| 213 | parent_path_ = trimmed_replacement.substr(0, trimmed_replacement.size() - 1); | |||
| 214 | } else { | |||
| 215 | parent_path_ = trimmed_replacement; | |||
| 216 | } | |||
| 217 | return (*this); | |||
| 218 | } | |||
| 219 | ||||
| 220 | TemporaryDirectory::TemporaryDirectory() { | |||
| 221 | char dir[]("/tmp/kea-tmpdir-XXXXXX"); | |||
| 222 | char const* dir_name = mkdtemp(dir); | |||
| 223 | if (!dir_name) { | |||
| 224 | isc_throw(Unexpected, "mkdtemp failed " << dir << ": " << strerror(errno))do { std::ostringstream oss__; oss__ << "mkdtemp failed " << dir << ": " << strerror((*__errno_location ())); throw Unexpected("../../../src/lib/util/filesystem.cc" , 224, oss__.str().c_str()); } while (1); | |||
| 225 | } | |||
| 226 | dir_name_ = string(dir_name); | |||
| 227 | } | |||
| 228 | ||||
| 229 | TemporaryDirectory::~TemporaryDirectory() { | |||
| 230 | DIR *dir(opendir(dir_name_.c_str())); | |||
| 231 | if (!dir) { | |||
| 232 | return; | |||
| 233 | } | |||
| 234 | ||||
| 235 | std::unique_ptr<DIR, void(*)(DIR*)> defer(dir, [](DIR* d) { closedir(d); }); | |||
| 236 | ||||
| 237 | struct dirent *i; | |||
| 238 | string filepath; | |||
| 239 | while ((i = readdir(dir))) { | |||
| 240 | if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) { | |||
| 241 | continue; | |||
| 242 | } | |||
| 243 | ||||
| 244 | filepath = dir_name_ + '/' + i->d_name; | |||
| 245 | remove(filepath.c_str()); | |||
| 246 | } | |||
| 247 | ||||
| 248 | rmdir(dir_name_.c_str()); | |||
| 249 | } | |||
| 250 | ||||
| 251 | string TemporaryDirectory::dirName() { | |||
| 252 | return dir_name_; | |||
| 253 | } | |||
| 254 | ||||
| 255 | PathChecker::PathChecker(const std::string default_path, | |||
| 256 | const std::string env_name /* = "" */) | |||
| 257 | : default_path_(default_path), env_name_(env_name), | |||
| 258 | default_overridden_(false) { | |||
| 259 | getPath(true); | |||
| ||||
| 260 | } | |||
| 261 | ||||
| 262 | std::string | |||
| 263 | PathChecker::getPath(bool reset /* = false */, | |||
| 264 | const std::string explicit_path /* = "" */) { | |||
| 265 | if (reset
| |||
| 266 | if (!explicit_path.empty()) { | |||
| 267 | path_ = explicit_path; | |||
| 268 | } else if (!env_name_.empty()) { | |||
| 269 | path_ = std::string(std::getenv(env_name_.c_str()) ? | |||
| 270 | std::getenv(env_name_.c_str()) : default_path_); | |||
| ||||
| 271 | } else { | |||
| 272 | path_ = default_path_; | |||
| 273 | } | |||
| 274 | ||||
| 275 | // Remove the trailing "/" if it is present so comparison to | |||
| 276 | // other Path::parentPath() works. | |||
| 277 | while (!path_.empty() && path_.back() == '/') { | |||
| 278 | path_.pop_back(); | |||
| 279 | } | |||
| 280 | ||||
| 281 | default_overridden_ = (path_ != default_path_); | |||
| 282 | } | |||
| 283 | ||||
| 284 | return (path_); | |||
| 285 | } | |||
| 286 | ||||
| 287 | std::string | |||
| 288 | PathChecker::validatePath(const std::string input_path_str, | |||
| 289 | bool enforce_path /* = PathChecker::shouldEnforceSecurity() */) const { | |||
| 290 | Path input_path(trim(input_path_str)); | |||
| 291 | auto filename = input_path.filename(); | |||
| 292 | if (filename.empty()) { | |||
| 293 | isc_throw(BadValue, "path: '" << input_path.str() << "' has no filename")do { std::ostringstream oss__; oss__ << "path: '" << input_path.str() << "' has no filename"; throw BadValue ("../../../src/lib/util/filesystem.cc", 293, oss__.str().c_str ()); } while (1); | |||
| 294 | } | |||
| 295 | ||||
| 296 | auto parent_path = input_path.parentPath(); | |||
| 297 | auto parent_dir = input_path.parentDirectory(); | |||
| 298 | if (!parent_dir.empty()) { | |||
| 299 | // We only allow absolute path equal to default. Catch an invalid path. | |||
| 300 | if ((parent_path != path_) || (parent_dir == "/")) { | |||
| 301 | std::ostringstream oss; | |||
| 302 | oss << "invalid path specified: '" | |||
| 303 | << (parent_path.empty() ? "/" : parent_path) | |||
| 304 | << "', supported path is '" | |||
| 305 | << path_ << "'"; | |||
| 306 | ||||
| 307 | if (enforce_path) { | |||
| 308 | isc_throw(SecurityError, oss.str())do { std::ostringstream oss__; oss__ << oss.str(); throw SecurityError("../../../src/lib/util/filesystem.cc", 308, oss__ .str().c_str()); } while (1); | |||
| 309 | } else { | |||
| 310 | isc_throw(SecurityWarn, oss.str())do { std::ostringstream oss__; oss__ << oss.str(); throw SecurityWarn("../../../src/lib/util/filesystem.cc", 310, oss__ .str().c_str()); } while (1); | |||
| 311 | } | |||
| 312 | } | |||
| 313 | } | |||
| 314 | ||||
| 315 | std::string valid_path(path_ + "/" + filename); | |||
| 316 | return (valid_path); | |||
| 317 | } | |||
| 318 | ||||
| 319 | std::string | |||
| 320 | PathChecker::validateDirectory(const std::string input_path_str, | |||
| 321 | bool enforce_path /* = PathChecker::shouldEnforceSecurity() */) const { | |||
| 322 | // We only allow absolute path equal to default. Catch an invalid path. | |||
| 323 | if (!input_path_str.empty()) { | |||
| 324 | std::string input_copy = input_path_str; | |||
| 325 | while (!input_copy.empty() && input_copy.back() == '/') { | |||
| 326 | input_copy.pop_back(); | |||
| 327 | } | |||
| 328 | ||||
| 329 | if (input_copy != path_) { | |||
| 330 | std::ostringstream oss; | |||
| 331 | oss << "invalid path specified: '" | |||
| 332 | << input_path_str << "', supported path is '" | |||
| 333 | << path_ << "'"; | |||
| 334 | ||||
| 335 | if (enforce_path) { | |||
| 336 | isc_throw(SecurityError, oss.str())do { std::ostringstream oss__; oss__ << oss.str(); throw SecurityError("../../../src/lib/util/filesystem.cc", 336, oss__ .str().c_str()); } while (1); | |||
| 337 | } else { | |||
| 338 | isc_throw(SecurityWarn, oss.str())do { std::ostringstream oss__; oss__ << oss.str(); throw SecurityWarn("../../../src/lib/util/filesystem.cc", 338, oss__ .str().c_str()); } while (1); | |||
| 339 | } | |||
| 340 | } | |||
| 341 | } | |||
| 342 | ||||
| 343 | return (path_); | |||
| 344 | } | |||
| 345 | ||||
| 346 | bool | |||
| 347 | PathChecker::pathHasPermissions(mode_t permissions, bool enforce_perms | |||
| 348 | /* = PathChecker::shouldEnforceSecurity() */) const { | |||
| 349 | return((!enforce_perms) || hasPermissions(path_, permissions)); | |||
| 350 | } | |||
| 351 | ||||
| 352 | bool | |||
| 353 | PathChecker::isDefaultOverridden() { | |||
| 354 | return (default_overridden_); | |||
| 355 | } | |||
| 356 | ||||
| 357 | bool PathChecker::shouldEnforceSecurity() { | |||
| 358 | return (enforce_security_); | |||
| 359 | } | |||
| 360 | ||||
| 361 | void PathChecker::enableEnforcement(bool enable) { | |||
| 362 | enforce_security_ = enable; | |||
| 363 | } | |||
| 364 | ||||
| 365 | bool PathChecker::enforce_security_ = true; | |||
| 366 | ||||
| 367 | } // namespace file | |||
| 368 | } // namespace util | |||
| 369 | } // namespace isc |