File: | home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-private/tmphs1g5w79/../../../src/lib/util/filesystem.cc |
Warning: | line 263, column 33 The parameter must not be null |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | // Copyright (C) 2021-2025 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 | 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 | bool amRunningAsRoot() { | |||
109 | return (getuid() == 0 || geteuid() == 0); | |||
110 | } | |||
111 | ||||
112 | Path::Path(string const& full_name) { | |||
113 | dir_present_ = false; | |||
114 | if (!full_name.empty()) { | |||
115 | // Find the directory. | |||
116 | size_t last_slash = full_name.find_last_of('/'); | |||
117 | if (last_slash != string::npos) { | |||
118 | // Found a directory so note the fact. | |||
119 | dir_present_ = true; | |||
120 | ||||
121 | // Found the last slash, so extract directory component and | |||
122 | // set where the scan for the last_dot should terminate. | |||
123 | parent_path_ = full_name.substr(0, last_slash); | |||
124 | if (last_slash == full_name.size()) { | |||
125 | // The entire string was a directory, so exit and don't | |||
126 | // do any more searching. | |||
127 | return; | |||
128 | } | |||
129 | } | |||
130 | ||||
131 | // Now search backwards for the last ".". | |||
132 | size_t last_dot = full_name.find_last_of('.'); | |||
133 | if ((last_dot == string::npos) || (dir_present_ && (last_dot < last_slash))) { | |||
134 | // Last "." either not found or it occurs to the left of the last | |||
135 | // slash if a directory was present (so it is part of a directory | |||
136 | // name). In this case, the remainder of the string after the slash | |||
137 | // is the name part. | |||
138 | stem_ = full_name.substr(last_slash + 1); | |||
139 | return; | |||
140 | } | |||
141 | ||||
142 | // Did find a valid dot, so it and everything to the right is the | |||
143 | // extension... | |||
144 | extension_ = full_name.substr(last_dot); | |||
145 | ||||
146 | // ... and the name of the file is everything in between. | |||
147 | if ((last_dot - last_slash) > 1) { | |||
148 | stem_ = full_name.substr(last_slash + 1, last_dot - last_slash - 1); | |||
149 | } | |||
150 | } | |||
151 | } | |||
152 | ||||
153 | string | |||
154 | Path::str() const { | |||
155 | return (parent_path_ + (dir_present_ ? "/" : "") + stem_ + extension_); | |||
156 | } | |||
157 | ||||
158 | string | |||
159 | Path::parentPath() const { | |||
160 | return (parent_path_); | |||
161 | } | |||
162 | ||||
163 | string | |||
164 | Path::parentDirectory() const { | |||
165 | return (parent_path_ + (dir_present_ ? "/" : "")); | |||
166 | } | |||
167 | ||||
168 | string | |||
169 | Path::stem() const { | |||
170 | return (stem_); | |||
171 | } | |||
172 | ||||
173 | string | |||
174 | Path::extension() const { | |||
175 | return (extension_); | |||
176 | } | |||
177 | ||||
178 | string | |||
179 | Path::filename() const { | |||
180 | return (stem_ + extension_); | |||
181 | } | |||
182 | ||||
183 | Path& | |||
184 | Path::replaceExtension(string const& replacement) { | |||
185 | string const trimmed_replacement(trim(replacement)); | |||
186 | if (trimmed_replacement.empty()) { | |||
187 | extension_ = string(); | |||
188 | } else { | |||
189 | size_t const last_dot(trimmed_replacement.find_last_of('.')); | |||
190 | if (last_dot == string::npos) { | |||
191 | extension_ = "." + trimmed_replacement; | |||
192 | } else { | |||
193 | extension_ = trimmed_replacement.substr(last_dot); | |||
194 | } | |||
195 | } | |||
196 | return (*this); | |||
197 | } | |||
198 | ||||
199 | Path& | |||
200 | Path::replaceParentPath(string const& replacement) { | |||
201 | string const trimmed_replacement(trim(replacement)); | |||
202 | dir_present_ = (trimmed_replacement.find_last_of('/') != string::npos); | |||
203 | if (trimmed_replacement.empty() || (trimmed_replacement == "/")) { | |||
204 | parent_path_ = string(); | |||
205 | } else if (trimmed_replacement.at(trimmed_replacement.size() - 1) == '/') { | |||
206 | parent_path_ = trimmed_replacement.substr(0, trimmed_replacement.size() - 1); | |||
207 | } else { | |||
208 | parent_path_ = trimmed_replacement; | |||
209 | } | |||
210 | return (*this); | |||
211 | } | |||
212 | ||||
213 | TemporaryDirectory::TemporaryDirectory() { | |||
214 | char dir[]("/tmp/kea-tmpdir-XXXXXX"); | |||
215 | char const* dir_name = mkdtemp(dir); | |||
216 | if (!dir_name) { | |||
217 | 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" , 217, oss__.str().c_str()); } while (1); | |||
218 | } | |||
219 | dir_name_ = string(dir_name); | |||
220 | } | |||
221 | ||||
222 | TemporaryDirectory::~TemporaryDirectory() { | |||
223 | DIR *dir(opendir(dir_name_.c_str())); | |||
224 | if (!dir) { | |||
225 | return; | |||
226 | } | |||
227 | ||||
228 | std::unique_ptr<DIR, void(*)(DIR*)> defer(dir, [](DIR* d) { closedir(d); }); | |||
229 | ||||
230 | struct dirent *i; | |||
231 | string filepath; | |||
232 | while ((i = readdir(dir))) { | |||
233 | if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) { | |||
234 | continue; | |||
235 | } | |||
236 | ||||
237 | filepath = dir_name_ + '/' + i->d_name; | |||
238 | remove(filepath.c_str()); | |||
239 | } | |||
240 | ||||
241 | rmdir(dir_name_.c_str()); | |||
242 | } | |||
243 | ||||
244 | string TemporaryDirectory::dirName() { | |||
245 | return dir_name_; | |||
246 | } | |||
247 | ||||
248 | PathChecker::PathChecker(const std::string default_path, | |||
249 | const std::string env_name /* = "" */) | |||
250 | : default_path_(default_path), env_name_(env_name), | |||
251 | default_overridden_(false) { | |||
252 | getPath(true); | |||
| ||||
253 | } | |||
254 | ||||
255 | std::string | |||
256 | PathChecker::getPath(bool reset /* = false */, | |||
257 | const std::string explicit_path /* = "" */) { | |||
258 | if (reset
| |||
259 | if (!explicit_path.empty()) { | |||
260 | path_ = explicit_path; | |||
261 | } else if (!env_name_.empty()) { | |||
262 | path_ = std::string(std::getenv(env_name_.c_str()) ? | |||
263 | std::getenv(env_name_.c_str()) : default_path_); | |||
| ||||
264 | } else { | |||
265 | path_ = default_path_; | |||
266 | } | |||
267 | ||||
268 | // Remove the trailing "/" if it is present so comparison to | |||
269 | // other Path::parentPath() works. | |||
270 | while (!path_.empty() && path_.back() == '/') { | |||
271 | path_.pop_back(); | |||
272 | } | |||
273 | ||||
274 | default_overridden_ = (path_ != default_path_); | |||
275 | } | |||
276 | ||||
277 | return (path_); | |||
278 | } | |||
279 | ||||
280 | std::string | |||
281 | PathChecker::validatePath(const std::string input_path_str, | |||
282 | bool enforce_path /* = PathChecker::shouldEnforceSecurity() */) const { | |||
283 | Path input_path(trim(input_path_str)); | |||
284 | auto filename = input_path.filename(); | |||
285 | if (filename.empty()) { | |||
286 | 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", 286, oss__.str().c_str ()); } while (1); | |||
287 | } | |||
288 | ||||
289 | auto parent_path = input_path.parentPath(); | |||
290 | auto parent_dir = input_path.parentDirectory(); | |||
291 | if (!parent_dir.empty()) { | |||
292 | // We only allow absolute path equal to default. Catch an invalid path. | |||
293 | if ((parent_path != path_) || (parent_dir == "/")) { | |||
294 | std::ostringstream oss; | |||
295 | oss << "invalid path specified: '" | |||
296 | << (parent_path.empty() ? "/" : parent_path) | |||
297 | << "', supported path is '" | |||
298 | << path_ << "'"; | |||
299 | ||||
300 | if (enforce_path) { | |||
301 | isc_throw(SecurityError, oss.str())do { std::ostringstream oss__; oss__ << oss.str(); throw SecurityError("../../../src/lib/util/filesystem.cc", 301, oss__ .str().c_str()); } while (1); | |||
302 | } else { | |||
303 | isc_throw(SecurityWarn, oss.str())do { std::ostringstream oss__; oss__ << oss.str(); throw SecurityWarn("../../../src/lib/util/filesystem.cc", 303, oss__ .str().c_str()); } while (1); | |||
304 | } | |||
305 | } | |||
306 | } | |||
307 | ||||
308 | std::string valid_path(path_ + "/" + filename); | |||
309 | return (valid_path); | |||
310 | } | |||
311 | ||||
312 | std::string | |||
313 | PathChecker::validateDirectory(const std::string input_path_str, | |||
314 | bool enforce_path /* = PathChecker::shouldEnforceSecurity() */) const { | |||
315 | std::string input_copy = trim(input_path_str); | |||
316 | // We only allow absolute path equal to default. Catch an invalid path. | |||
317 | if (!input_path_str.empty()) { | |||
318 | std::string input_copy = input_path_str; | |||
319 | while (!input_copy.empty() && input_copy.back() == '/') { | |||
320 | input_copy.pop_back(); | |||
321 | } | |||
322 | ||||
323 | if (input_copy != path_) { | |||
324 | std::ostringstream oss; | |||
325 | oss << "invalid path specified: '" | |||
326 | << input_path_str << "', supported path is '" | |||
327 | << path_ << "'"; | |||
328 | ||||
329 | if (enforce_path) { | |||
330 | isc_throw(SecurityError, oss.str())do { std::ostringstream oss__; oss__ << oss.str(); throw SecurityError("../../../src/lib/util/filesystem.cc", 330, oss__ .str().c_str()); } while (1); | |||
331 | } else { | |||
332 | isc_throw(SecurityWarn, oss.str())do { std::ostringstream oss__; oss__ << oss.str(); throw SecurityWarn("../../../src/lib/util/filesystem.cc", 332, oss__ .str().c_str()); } while (1); | |||
333 | } | |||
334 | } | |||
335 | } | |||
336 | ||||
337 | return (path_); | |||
338 | } | |||
339 | ||||
340 | bool | |||
341 | PathChecker::pathHasPermissions(mode_t permissions, bool enforce_perms | |||
342 | /* = PathChecker::shouldEnforceSecurity() */) const { | |||
343 | return((!enforce_perms) || hasPermissions(path_, permissions)); | |||
344 | } | |||
345 | ||||
346 | bool | |||
347 | PathChecker::isDefaultOverridden() { | |||
348 | return (default_overridden_); | |||
349 | } | |||
350 | ||||
351 | bool PathChecker::shouldEnforceSecurity() { | |||
352 | return (enforce_security_); | |||
353 | } | |||
354 | ||||
355 | void PathChecker::enableEnforcement(bool enable) { | |||
356 | enforce_security_ = enable; | |||
357 | } | |||
358 | ||||
359 | bool PathChecker::enforce_security_ = true; | |||
360 | ||||
361 | } // namespace file | |||
362 | } // namespace util | |||
363 | } // namespace isc |