Coverage Report

Created: 2023-09-25 07:09

/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 */