/src/usbguard/src/Common/Utility.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // |
2 | | // Copyright (C) 2015 Red Hat, Inc. |
3 | | // |
4 | | // This program is free software; you can redistribute it and/or modify |
5 | | // it under the terms of the GNU General Public License as published by |
6 | | // the Free Software Foundation; either version 2 of the License, or |
7 | | // (at your option) any later version. |
8 | | // |
9 | | // This program is distributed in the hope that it will be useful, |
10 | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | // GNU General Public License for more details. |
13 | | // |
14 | | // You should have received a copy of the GNU General Public License |
15 | | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | | // |
17 | | // Authors: Daniel Kopecek <dkopecek@redhat.com> |
18 | | // |
19 | | #ifdef HAVE_BUILD_CONFIG_H |
20 | | #include <build-config.h> |
21 | | #endif |
22 | | |
23 | | #include "usbguard/Logger.hpp" |
24 | | |
25 | | #include "Common/Utility.hpp" |
26 | | |
27 | | #include <fstream> |
28 | | #include <algorithm> |
29 | | |
30 | | #include <alloca.h> |
31 | | #include <ctype.h> |
32 | | #include <fcntl.h> |
33 | | #include <stdio.h> |
34 | | #include <stdlib.h> |
35 | | #include <sys/resource.h> |
36 | | #include <sys/stat.h> |
37 | | #include <sys/time.h> |
38 | | #include <sys/types.h> |
39 | | #include <sys/wait.h> |
40 | | #include <unistd.h> |
41 | | |
42 | | namespace usbguard |
43 | | { |
44 | | |
45 | | static void runCommandExecChild(const std::string& path, const std::vector<std::string>& args) |
46 | 0 | { |
47 | 0 | struct rlimit rlim; |
48 | | |
49 | | // Find out what the maxfd value could be |
50 | 0 | if (::getrlimit(RLIMIT_NOFILE, &rlim) == -1) { |
51 | 0 | return; |
52 | 0 | } |
53 | | |
54 | 0 | const int maxfd = (rlim.rlim_max == RLIM_INFINITY ? 4096 : rlim.rlim_max); |
55 | 0 | const int nullfd = ::open("/dev/null", O_RDWR); |
56 | |
|
57 | 0 | if (nullfd < 0) { |
58 | 0 | return; |
59 | 0 | } |
60 | | |
61 | 0 | for (int fd = 0; fd < maxfd; ++fd) { |
62 | 0 | if (fd == nullfd) { |
63 | | // Don't close our /dev/null fd |
64 | 0 | continue; |
65 | 0 | } |
66 | | |
67 | 0 | switch (fd) { |
68 | 0 | case STDERR_FILENO: |
69 | 0 | case STDOUT_FILENO: |
70 | 0 | case STDIN_FILENO: |
71 | | // Redirect the standard descriptors to /dev/null |
72 | 0 | ::dup2(nullfd, fd); |
73 | 0 | break; |
74 | | |
75 | 0 | default: |
76 | | // Close everything else |
77 | 0 | (void)::close(fd); |
78 | 0 | } |
79 | 0 | } |
80 | | |
81 | 0 | (void)::close(nullfd); |
82 | |
|
83 | 0 | if (args.size() > 1024) { |
84 | | // Looks suspicious... |
85 | 0 | return; |
86 | 0 | } |
87 | | |
88 | | // |
89 | | // Allocate space for the argv array on stack |
90 | | // +1 ... for argv[0] |
91 | | // +1 ... for nullptr termination of the array |
92 | | // |
93 | 0 | char** const args_cstr = (char**)(::alloca(sizeof(const char*) * (args.size() + 2))); |
94 | 0 | unsigned int i; |
95 | 0 | args_cstr[0] = const_cast<char*>(path.c_str()); |
96 | |
|
97 | 0 | for (i = 0; i < args.size(); ++i) { |
98 | 0 | args_cstr[1+i] = const_cast<char*>(args[i].c_str()); |
99 | 0 | } |
100 | |
|
101 | 0 | args_cstr[1+i] = nullptr; |
102 | | // TODO: Reset environment? |
103 | 0 | (void)::execv(path.c_str(), args_cstr); |
104 | 0 | } |
105 | | |
106 | | int runCommand(const char* const path, const char* const arg1, const int timeout_secs) |
107 | 0 | { |
108 | 0 | std::vector<std::string> args; |
109 | 0 | args.push_back(arg1); |
110 | 0 | return runCommand(path, args, timeout_secs); |
111 | 0 | } |
112 | | |
113 | | int runCommand(const char* const path, const char* const arg1, const char* const arg2, const int timeout_secs) |
114 | 0 | { |
115 | 0 | std::vector<std::string> args; |
116 | 0 | args.push_back(arg1); |
117 | 0 | args.push_back(arg2); |
118 | 0 | return runCommand(path, args, timeout_secs); |
119 | 0 | } |
120 | | |
121 | | int runCommand(const std::string& path, const std::vector<std::string>& args, const int timeout_secs) |
122 | 0 | { |
123 | 0 | int retval = 0, status = 0; |
124 | 0 | bool timedout = false; |
125 | 0 | const pid_t child_pid = ::fork(); |
126 | |
|
127 | 0 | switch (child_pid) { |
128 | 0 | case 0: |
129 | | // Child |
130 | 0 | runCommandExecChild(path, args); |
131 | 0 | ::_exit(EXIT_FAILURE); |
132 | | |
133 | 0 | case -1: |
134 | 0 | default: |
135 | 0 | break; |
136 | 0 | } |
137 | | |
138 | | // Parent - wait for the child to exit (up to timeout seconds) |
139 | 0 | int waitpid_time_usec = timeout_secs * 1000 * 1000; |
140 | |
|
141 | 0 | while (waitpid_time_usec > 0) { |
142 | 0 | const pid_t waitpid_retval = ::waitpid(child_pid, &status, WNOHANG); |
143 | 0 | timedout = false; |
144 | |
|
145 | 0 | switch (waitpid_retval) { |
146 | 0 | case 0: // Not exited yet; Sleep & retry |
147 | 0 | timedout = true; |
148 | 0 | waitpid_time_usec -= 500; |
149 | 0 | ::usleep(500); |
150 | 0 | continue; |
151 | | |
152 | 0 | case -1: // Error |
153 | 0 | retval = -1; |
154 | 0 | break; |
155 | | |
156 | 0 | default: |
157 | 0 | if (waitpid_retval == child_pid) { |
158 | | // Child exited |
159 | 0 | retval = WEXITSTATUS(status); |
160 | 0 | waitpid_time_usec = 0; |
161 | 0 | continue; |
162 | 0 | } |
163 | 0 | else { |
164 | | // Unexpected error |
165 | 0 | } |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | 0 | if (timedout) { |
170 | | // Try to be nice first |
171 | 0 | ::kill(child_pid, SIGTERM); |
172 | 0 | ::usleep(1000*500); |
173 | | |
174 | | // Send SIGKILL if the process is still running |
175 | 0 | if (::waitpid(child_pid, &status, WNOHANG) != child_pid) { |
176 | 0 | ::kill(child_pid, SIGKILL); |
177 | 0 | } |
178 | |
|
179 | 0 | retval = -1; |
180 | 0 | } |
181 | |
|
182 | 0 | return retval; |
183 | 0 | } |
184 | | |
185 | | std::string filenameFromPath(const std::string& filepath, const bool include_extension) |
186 | 0 | { |
187 | 0 | const std::string directory_separator = "/"; |
188 | 0 | std::vector<std::string> path_tokens; |
189 | 0 | tokenizeString(filepath, path_tokens, directory_separator); |
190 | |
|
191 | 0 | if (path_tokens.size() == 0) { |
192 | 0 | return std::string(); |
193 | 0 | } |
194 | | |
195 | 0 | const std::string& filename = path_tokens.back(); |
196 | |
|
197 | 0 | if (include_extension) { |
198 | 0 | return filename; |
199 | 0 | } |
200 | | |
201 | 0 | const size_t substr_to = filename.find_last_of('.'); |
202 | 0 | return filename.substr(0, substr_to); |
203 | 0 | } |
204 | | |
205 | | std::string parentPath(const std::string& path) |
206 | 0 | { |
207 | 0 | const std::string directory_separator = "/"; |
208 | 0 | std::string parent_path(path); |
209 | | // find first not '/' (from end) |
210 | | // find first '/' (from end) |
211 | | // find first not '/' (from end) |
212 | 0 | auto reverse_start_pos = \ |
213 | 0 | parent_path.find_last_not_of(directory_separator); |
214 | | |
215 | | /* |
216 | | * Whole path consists only of '/'. |
217 | | */ |
218 | 0 | if (reverse_start_pos == std::string::npos) { |
219 | 0 | return std::string(); |
220 | 0 | } |
221 | | |
222 | 0 | reverse_start_pos = \ |
223 | 0 | parent_path.find_last_of(directory_separator, reverse_start_pos); |
224 | | |
225 | | /* |
226 | | * No directory separator in the rest of the path. |
227 | | */ |
228 | 0 | if (reverse_start_pos == std::string::npos) { |
229 | 0 | return std::string(); |
230 | 0 | } |
231 | | |
232 | 0 | reverse_start_pos = \ |
233 | 0 | parent_path.find_last_not_of(directory_separator, reverse_start_pos); |
234 | | |
235 | | /* |
236 | | * |
237 | | * /foo/bar => /foo |
238 | | * /foo/bar/ => /foo |
239 | | * /foo/bar// => /foo |
240 | | * /foo => std::string() |
241 | | * /foo/ => std::string() |
242 | | * / => std::string() |
243 | | * //foo => std::string() |
244 | | * |
245 | | */ |
246 | 0 | if (reverse_start_pos == std::string::npos) { |
247 | 0 | return std::string(); |
248 | 0 | } |
249 | | |
250 | 0 | return path.substr(0, reverse_start_pos + 1); |
251 | 0 | } |
252 | | |
253 | | std::string trimRight(const std::string& s, const std::string& delimiters) |
254 | 126k | { |
255 | 126k | const size_t substr_to = s.find_last_not_of(delimiters); |
256 | | |
257 | 126k | if (substr_to != std::string::npos) { |
258 | 126k | return s.substr(0, substr_to + 1); |
259 | 126k | } |
260 | 0 | else { |
261 | 0 | return std::string(); |
262 | 0 | } |
263 | 126k | } |
264 | | |
265 | | std::string trimLeft(const std::string& s, const std::string& delimiters) |
266 | 126k | { |
267 | 126k | const size_t substr_from = s.find_first_not_of(delimiters); |
268 | | |
269 | 126k | if (substr_from == std::string::npos) { |
270 | 0 | return s; |
271 | 0 | } |
272 | 126k | else { |
273 | 126k | return s.substr(substr_from); |
274 | 126k | } |
275 | 126k | } |
276 | | |
277 | | std::string trim(const std::string& s, const std::string& delimiters) |
278 | 126k | { |
279 | 126k | return trimRight(trimLeft(s, delimiters), delimiters); |
280 | 126k | } |
281 | | |
282 | | /* |
283 | | * The ostringstream class used for the implementation of numberToString |
284 | | * treats (u)int8_t as a char. We want to treat it as a number, so this |
285 | | * explicit specialization handles the uint8_t case by recasting it to |
286 | | * an unsigned int. |
287 | | */ |
288 | | template<> |
289 | | std::string numberToString(const uint8_t number, const std::string& prefix, const int base, const int align, |
290 | | const char align_char) |
291 | 0 | { |
292 | 0 | const uint16_t n = static_cast<uint16_t>(number); |
293 | 0 | return numberToString(n, prefix, base, align, align_char); |
294 | 0 | } |
295 | | |
296 | | template<> |
297 | | uint8_t stringToNumber(const std::string& s, const int base) |
298 | 0 | { |
299 | 0 | const unsigned int num = stringToNumber<unsigned int>(s, base); |
300 | 0 | return (uint8_t)num; |
301 | 0 | } |
302 | | |
303 | | bool isNumericString(const std::string& s) |
304 | 0 | { |
305 | 0 | for (int c : s) { |
306 | 0 | if (!isdigit(c)) { |
307 | 0 | return false; |
308 | 0 | } |
309 | 0 | } |
310 | | |
311 | 0 | return true; |
312 | 0 | } |
313 | | |
314 | | int loadFiles(const std::string& directory, |
315 | | std::function<std::string(const std::string&, const struct dirent*)> filter, |
316 | | std::function<int(const std::string&, const std::string&)> loader, |
317 | | std::function<bool(const std::pair<std::string, std::string>&, const std::pair<std::string, std::string>&)> sorter, |
318 | | bool directory_required) |
319 | 0 | { |
320 | 0 | DIR* dirobj = opendir(directory.c_str()); |
321 | |
|
322 | 0 | if (dirobj == nullptr) { |
323 | 0 | if (!directory_required && errno == ENOENT) { |
324 | 0 | return 0; |
325 | 0 | } |
326 | 0 | else { |
327 | 0 | throw ErrnoException("loadFiles", directory, errno); |
328 | 0 | } |
329 | 0 | } |
330 | | |
331 | 0 | int retval = 0; |
332 | |
|
333 | 0 | try { |
334 | 0 | std::vector<std::pair<std::string, std::string>> loadpaths; |
335 | 0 | struct dirent* entry_ptr = nullptr; |
336 | | |
337 | | /* |
338 | | * readdir usage note: We rely on the fact that readdir should be thread-safe |
339 | | * when used on a different directory stream. Since we create our own stream, |
340 | | * we should be fine with readdir here. The thread-safe version of readdir, |
341 | | * readdir_r, is deprecated in newer versions of glibc. |
342 | | */ |
343 | 0 | while ((entry_ptr = readdir(dirobj)) != nullptr) { |
344 | 0 | const std::string filename(entry_ptr->d_name); |
345 | |
|
346 | 0 | if (filename == "." || filename == "..") { |
347 | 0 | continue; |
348 | 0 | } |
349 | | |
350 | 0 | std::string fullpath = directory + "/" + filename; |
351 | 0 | std::string loadpath = filter(fullpath, entry_ptr); |
352 | |
|
353 | 0 | if (!loadpath.empty()) { |
354 | 0 | loadpaths.emplace_back(std::make_pair(std::move(loadpath), std::move(fullpath))); |
355 | 0 | } |
356 | 0 | } |
357 | |
|
358 | 0 | std::sort(loadpaths.begin(), loadpaths.end(), sorter); |
359 | |
|
360 | 0 | for (const auto& loadpath : loadpaths) { |
361 | 0 | USBGUARD_LOG(Trace) << "L: " << loadpath.first << " : " << loadpath.second; |
362 | 0 | } |
363 | |
|
364 | 0 | for (const auto& loadpath : loadpaths) { |
365 | 0 | retval += loader(loadpath.first, loadpath.second); |
366 | 0 | } |
367 | 0 | } |
368 | 0 | catch (...) { |
369 | 0 | closedir(dirobj); |
370 | 0 | throw; |
371 | 0 | } |
372 | | |
373 | 0 | closedir(dirobj); |
374 | 0 | return retval; |
375 | 0 | } |
376 | | |
377 | | std::string removePrefix(const std::string& prefix, const std::string& value) |
378 | 0 | { |
379 | 0 | if (value.compare(0, prefix.size(), prefix) == 0) { |
380 | 0 | return value.substr(prefix.size()); |
381 | 0 | } |
382 | 0 | else { |
383 | 0 | return value; |
384 | 0 | } |
385 | 0 | } |
386 | | |
387 | | bool hasSuffix(const std::string& value, const std::string& suffix) |
388 | 0 | { |
389 | 0 | if (suffix.size() > value.size()) { |
390 | 0 | return false; |
391 | 0 | } |
392 | | |
393 | 0 | const auto pos = value.size() - suffix.size(); |
394 | 0 | const auto cmp = value.compare(pos, suffix.size(), suffix); |
395 | 0 | return cmp == 0; |
396 | 0 | } |
397 | | |
398 | | bool hasPrefix(const std::string& value, const std::string& prefix) |
399 | 0 | { |
400 | 0 | if (prefix.size() > value.size()) { |
401 | 0 | return false; |
402 | 0 | } |
403 | | |
404 | 0 | const auto cmp = value.compare(0, prefix.size(), prefix); |
405 | 0 | return cmp == 0; |
406 | 0 | } |
407 | | |
408 | | std::string symlinkPath(const std::string& linkpath, struct stat* st_user) |
409 | 0 | { |
410 | 0 | struct stat st = { }; |
411 | 0 | struct stat* st_ptr = nullptr; |
412 | |
|
413 | 0 | if (st_user == nullptr) { |
414 | 0 | USBGUARD_SYSCALL_THROW("symlinkPath", |
415 | 0 | lstat(linkpath.c_str(), &st) != 0); |
416 | 0 | st_ptr = &st; |
417 | 0 | } |
418 | 0 | else { |
419 | 0 | st_ptr = st_user; |
420 | 0 | } |
421 | | |
422 | 0 | if (!S_ISLNK(st_ptr->st_mode)) { |
423 | 0 | throw Exception("symlinkPath", linkpath, "not a symlink"); |
424 | 0 | } |
425 | | |
426 | 0 | if (st_ptr->st_size < 1) { |
427 | 0 | st_ptr->st_size = 4096; |
428 | 0 | } |
429 | | |
430 | | /* |
431 | | * Check sanity of st_size. min: 1 byte, max: 1 MiB (because 1 MiB should be enough :) |
432 | | */ |
433 | 0 | if (st_ptr->st_size < 1 || st_ptr->st_size > (1024 * 1024)) { |
434 | 0 | USBGUARD_LOG(Debug) << "st_size=" << st_ptr->st_size; |
435 | 0 | throw Exception("symlinkPath", linkpath, "symlink value size out of range"); |
436 | 0 | } |
437 | | |
438 | 0 | std::string buffer(st_ptr->st_size, 0); |
439 | 0 | const ssize_t link_size = readlink(linkpath.c_str(), &buffer[0], buffer.capacity()); |
440 | |
|
441 | 0 | if (link_size <= 0 || link_size > st_ptr->st_size) { |
442 | 0 | USBGUARD_LOG(Debug) << "link_size=" << link_size |
443 | 0 | << " st_size=" << st_ptr->st_size; |
444 | 0 | throw Exception("symlinkPath", linkpath, "symlink value size changed before read"); |
445 | 0 | } |
446 | | |
447 | 0 | buffer.resize(link_size); |
448 | |
|
449 | 0 | if (buffer[0] == '/') { |
450 | | /* Absolute path */ |
451 | 0 | return buffer; |
452 | 0 | } |
453 | 0 | else { |
454 | | /* Relative path */ |
455 | 0 | return parentPath(linkpath) + "/" + buffer; |
456 | 0 | } |
457 | 0 | } |
458 | | |
459 | | std::size_t countPathComponents(const std::string& path) |
460 | 0 | { |
461 | 0 | bool was_component = false; |
462 | 0 | std::size_t count = 0; |
463 | |
|
464 | 0 | for (std::size_t i = 0; i < path.size(); ++i) { |
465 | 0 | const char c = path[i]; |
466 | |
|
467 | 0 | if (c == '/') { |
468 | 0 | if (was_component) { |
469 | 0 | ++count; |
470 | 0 | was_component = false; |
471 | 0 | } |
472 | 0 | } |
473 | 0 | else { |
474 | 0 | was_component = true; |
475 | 0 | } |
476 | 0 | } |
477 | |
|
478 | 0 | return count; |
479 | 0 | } |
480 | | |
481 | | std::string normalizePath(const std::string& path) |
482 | 0 | { |
483 | 0 | std::vector<std::string> components; |
484 | 0 | const bool is_absolute = (path[0] == '/'); |
485 | 0 | tokenizeString(path, components, "/", /*trim_empty=*/true); |
486 | |
|
487 | 0 | for (auto it = components.begin(); it != components.end();) { |
488 | 0 | if (*it == ".") { |
489 | | /* remove this component */ |
490 | 0 | it = components.erase(it); |
491 | 0 | } |
492 | 0 | else if (*it == "..") { |
493 | | /* remove this and previous component (if any) */ |
494 | 0 | if (it != components.begin()) { |
495 | 0 | it = components.erase(it - 1); |
496 | 0 | } |
497 | |
|
498 | 0 | it = components.erase(it); |
499 | 0 | } |
500 | 0 | else { |
501 | 0 | ++it; |
502 | 0 | } |
503 | 0 | } |
504 | |
|
505 | 0 | std::string normalized_path(is_absolute ? "/" : ""); |
506 | |
|
507 | 0 | for (auto it = components.cbegin(); it != components.cend(); ++it) { |
508 | 0 | normalized_path.append(*it); |
509 | |
|
510 | 0 | if ((it+1) != components.cend()) { |
511 | 0 | normalized_path.append("/"); |
512 | 0 | } |
513 | 0 | } |
514 | |
|
515 | 0 | return normalized_path; |
516 | 0 | } |
517 | | |
518 | | std::vector<std::string> getConfigsFromDir(const std::string& path) |
519 | 0 | { |
520 | 0 | std::vector<std::string> rulefile_list; |
521 | 0 | DIR* dir_fd = opendir(path.c_str()); |
522 | 0 | struct dirent* dp; |
523 | 0 | struct stat path_stat; |
524 | 0 | std::string file_name; |
525 | |
|
526 | 0 | if (!dir_fd) { |
527 | 0 | throw Exception("getConfigsFromDir", "opendir: " + path, strerror(errno)); |
528 | 0 | } |
529 | | |
530 | 0 | while ((dp = readdir(dir_fd)) != NULL) { // iterate over directory for file entries |
531 | 0 | file_name = path + '/' + dp->d_name; |
532 | |
|
533 | 0 | if (stat(file_name.c_str(), &path_stat) == 0) { |
534 | 0 | if (S_ISREG(path_stat.st_mode)) { // check if entry is a file |
535 | 0 | rulefile_list.push_back(file_name); // add it to output |
536 | 0 | } |
537 | 0 | } |
538 | 0 | } |
539 | | |
540 | | // cleanup |
541 | 0 | closedir(dir_fd); |
542 | 0 | std::sort(rulefile_list.begin(), rulefile_list.end()); |
543 | 0 | return rulefile_list; |
544 | 0 | } |
545 | | |
546 | | bool isValidName(const std::string& name) |
547 | 0 | { |
548 | 0 | const char* s = name.data(); |
549 | |
|
550 | 0 | if (('\0' == *s) || |
551 | 0 | !((('a' <= *s) && ('z' >= *s)) || |
552 | 0 | (('A' <= *s) && ('Z' >= *s)) || |
553 | 0 | ('_' == *s))) { |
554 | 0 | return false; |
555 | 0 | } |
556 | | |
557 | 0 | while ('\0' != *++s) { |
558 | 0 | if (!((('a' <= *s) && ('z' >= *s)) || |
559 | 0 | (('A' <= *s) && ('Z' >= *s)) || |
560 | 0 | (('0' <= *s) && ('9' >= *s)) || |
561 | 0 | ('_' == *s) || |
562 | 0 | ('-' == *s) || |
563 | 0 | (('$' == *s) && ('\0' == *(s + 1))))) { |
564 | 0 | return false; |
565 | 0 | } |
566 | 0 | } |
567 | | |
568 | 0 | return true; |
569 | 0 | } |
570 | | |
571 | | } /* namespace usbguard */ |
572 | | |
573 | | /* vim: set ts=2 sw=2 et */ |