Bug Summary

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

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-redhat-linux-gnu -O2 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name filesystem.cc -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-private/tmpxsedttj5 -fcoverage-compilation-dir=/home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-private/tmpxsedttj5 -resource-dir /usr/bin/../lib/clang/21 -I src/lib/util/libkea-util.so.116.0.0.p -I src/lib/util -I ../../../src/lib/util -I . -I ../../.. -I src -I ../../../src -I src/bin -I ../../../src/bin -I src/lib -I ../../../src/lib -I /usr/include -D _GLIBCXX_ASSERTIONS=1 -D _FILE_OFFSET_BITS=64 -D BOOST_ALL_NO_LIB -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/15/../../../../include/c++/15 -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/15/../../../../include/c++/15/x86_64-redhat-linux -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/15/../../../../include/c++/15/backward -internal-isystem /usr/bin/../lib/clang/21/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/15/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wwrite-strings -Wno-missing-field-initializers -fdeprecated-macro -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -fcolor-diagnostics -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-logs/scanbuild/2026-02-20-145647-4869-1 -x c++ ../../../src/lib/util/filesystem.cc
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
23using namespace isc;
24using namespace isc::util::str;
25using namespace std;
26
27namespace isc {
28namespace util {
29namespace file {
30
31
32string
33getContent(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
49bool
50exists(string const& path) {
51 struct stat statbuf;
52 return (::stat(path.c_str(), &statbuf) == 0);
53}
54
55mode_t
56getPermissions(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
65bool
66hasPermissions(const std::string path, const mode_t& permissions) {
67 return (getPermissions(path) == permissions);
68}
69
70bool
71isDir(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
79bool
80isFile(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
88bool
89isSocket(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
97void
98setUmask() {
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
108RelaxUmask::RelaxUmask() : orig_umask_(umask(S_IRWXO(((0400|0200|0100) >> 3) >> 3))) {
109}
110
111RelaxUmask::~RelaxUmask() {
112 static_cast<void>(umask(orig_umask_));
113}
114
115bool amRunningAsRoot() {
116 return (getuid() == 0 || geteuid() == 0);
117}
118
119Path::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
160string
161Path::str() const {
162 return (parent_path_ + (dir_present_ ? "/" : "") + stem_ + extension_);
163}
164
165string
166Path::parentPath() const {
167 return (parent_path_);
168}
169
170string
171Path::parentDirectory() const {
172 return (parent_path_ + (dir_present_ ? "/" : ""));
173}
174
175string
176Path::stem() const {
177 return (stem_);
178}
179
180string
181Path::extension() const {
182 return (extension_);
183}
184
185string
186Path::filename() const {
187 return (stem_ + extension_);
188}
189
190Path&
191Path::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
206Path&
207Path::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
220TemporaryDirectory::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
229TemporaryDirectory::~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
251string TemporaryDirectory::dirName() {
252 return dir_name_;
253}
254
255PathChecker::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);
1
Calling 'PathChecker::getPath'
260}
261
262std::string
263PathChecker::getPath(bool reset /* = false */,
264 const std::string explicit_path /* = "" */) {
265 if (reset
1.1
'reset' is true
) {
2
Taking true branch
266 if (!explicit_path.empty()) {
3
Assuming the condition is false
4
Taking false branch
267 path_ = explicit_path;
268 } else if (!env_name_.empty()) {
5
Assuming the condition is true
6
Taking true branch
269 path_ = std::string(std::getenv(env_name_.c_str()) ?
7
'?' condition is true
270 std::getenv(env_name_.c_str()) : default_path_);
8
The parameter must not be null
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
287std::string
288PathChecker::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
319std::string
320PathChecker::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
346bool
347PathChecker::pathHasPermissions(mode_t permissions, bool enforce_perms
348 /* = PathChecker::shouldEnforceSecurity() */) const {
349 return((!enforce_perms) || hasPermissions(path_, permissions));
350}
351
352bool
353PathChecker::isDefaultOverridden() {
354 return (default_overridden_);
355}
356
357bool PathChecker::shouldEnforceSecurity() {
358 return (enforce_security_);
359}
360
361void PathChecker::enableEnforcement(bool enable) {
362 enforce_security_ = enable;
363}
364
365bool PathChecker::enforce_security_ = true;
366
367} // namespace file
368} // namespace util
369} // namespace isc