Skip to content
API Documentation
BastionGuard™
ActionHelper.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-3.0-or-later
7
8#include <algorithm>
9#include <cerrno>
10#include <chrono>
11#include <cstring>
12#include <filesystem>
13#include <fstream>
14#include <fcntl.h>
15#include <iomanip>
16#include <iostream>
17#include <map>
18#include <optional>
19#include <sstream>
20#include <set>
21#include <vector>
22#include <stdexcept>
23#include <string>
24#include <string_view>
25#include <sys/stat.h>
26#include <sys/types.h>
27#include <unistd.h>
28
29namespace fs = std::filesystem;
30using namespace bastionguard::rootguard;
31
32namespace {
33
34constexpr const char* kDefaultPolicy = "/etc/bastionguard/rootguard.conf";
35constexpr const char* kQuarantineDir = "/var/lib/bastionguard/rootguard/quarantine";
36
37struct Options {
38 std::string command;
40 fs::path path;
41 std::optional<std::uint32_t> mode;
42 std::optional<std::uint32_t> uid;
43 std::optional<std::uint32_t> gid;
44 std::optional<std::uint64_t> device;
45 std::optional<std::uint64_t> inode;
46 std::string protection;
47 std::vector<fs::path> trustedApplications;
48 std::vector<fs::path> trustedExecutables;
49 std::vector<fs::path> deniedExecutables;
50};
51
52[[noreturn]] void fail(const std::string& message)
53{
54 throw std::runtime_error(message);
55}
56
57std::uint64_t parseUnsigned(const std::string& value, const char* label,
58 const int base = 10)
59{
60 std::size_t consumed = 0;
61 unsigned long long parsed = 0;
62 try {
63 parsed = std::stoull(value, &consumed, base);
64 } catch (...) {
65 fail(std::string("Invalid ") + label + ": " + value);
66 }
67 if (consumed != value.size())
68 fail(std::string("Invalid ") + label + ": " + value);
69 return static_cast<std::uint64_t>(parsed);
70}
71
72Options parseOptions(const int argc, char* argv[])
73{
74 if (argc < 2)
75 fail("Usage: bastionguard-rootguard-action <set-protection|set-application-rules|restore|remove|block-service|unblock-service> [options]");
76
77 Options options;
78 options.command = argv[1];
79 for (int index = 2; index < argc; ++index) {
80 const std::string key = argv[index];
81 if (index + 1 >= argc)
82 fail("Missing value for " + key);
83 const std::string value = argv[++index];
84 if (key == "--policy") options.policy = value;
85 else if (key == "--path") options.path = value;
86 else if (key == "--mode") options.mode = static_cast<std::uint32_t>(parseUnsigned(value, "mode", 8));
87 else if (key == "--uid") options.uid = static_cast<std::uint32_t>(parseUnsigned(value, "uid"));
88 else if (key == "--gid") options.gid = static_cast<std::uint32_t>(parseUnsigned(value, "gid"));
89 else if (key == "--device") options.device = parseUnsigned(value, "device");
90 else if (key == "--inode") options.inode = parseUnsigned(value, "inode");
91 else if (key == "--state") options.protection = value;
92 else if (key == "--trusted-application") options.trustedApplications.emplace_back(value);
93 else if (key == "--trusted-executable") options.trustedExecutables.emplace_back(value);
94 else if (key == "--denied-executable") options.deniedExecutables.emplace_back(value);
95 else fail("Unknown option: " + key);
96 }
97 return options;
98}
99
100bool pathStartsWith(const fs::path& path, const fs::path& prefix)
101{
102 const auto normalizedPath = path.lexically_normal();
103 const auto normalizedPrefix = prefix.lexically_normal();
104 auto pathIt = normalizedPath.begin();
105 for (auto prefixIt = normalizedPrefix.begin(); prefixIt != normalizedPrefix.end();
106 ++prefixIt, ++pathIt) {
107 if (pathIt == normalizedPath.end() || *pathIt != *prefixIt)
108 return false;
109 }
110 return true;
111}
112
113fs::path normalizedAbsolute(const fs::path& path)
114{
115 if (path.empty() || !path.is_absolute())
116 fail("An absolute path is required.");
117 const auto normalized = path.lexically_normal();
118 for (const auto& component : normalized) {
119 if (component == "..")
120 fail("Parent traversal is not allowed.");
121 }
122 return normalized;
123}
124
125bool isIgnoredPath(const Policy& policy, const fs::path& requested)
126{
127 const auto path = normalizedAbsolute(requested);
128 for (const auto& rule : policy.ignoredPaths) {
129 const auto ignoredPath = rule.path.lexically_normal();
130 if (rule.recursive ? pathStartsWith(path, ignoredPath)
131 : path == ignoredPath)
132 return true;
133 }
134 return false;
135}
136
137bool isProtectedPath(const Policy& policy, const fs::path& requested)
138{
139 const auto path = normalizedAbsolute(requested);
140 if (isIgnoredPath(policy, path))
141 return false;
142
143 for (const auto& rule : policy.protectedPaths) {
144 const auto protectedPath = rule.path.lexically_normal();
145 if (rule.recursive ? pathStartsWith(path, protectedPath)
146 : path == protectedPath)
147 return true;
148 }
149 return false;
150}
151
152void requireProtectedPath(const Policy& policy, const fs::path& path)
153{
154 if (!isProtectedPath(policy, path))
155 fail("The requested path is not covered by the RootGuard protected-path policy.");
156}
157
158std::string timestamp()
159{
160 const auto now = std::chrono::system_clock::now();
161 const auto time = std::chrono::system_clock::to_time_t(now);
162 std::tm tm{};
163 localtime_r(&time, &tm);
164 std::ostringstream output;
165 output << std::put_time(&tm, "%Y%m%d-%H%M%S");
166 return output.str();
167}
168
169void copyString(char* destination, const std::size_t size,
170 const std::string& source)
171{
172 if (size == 0)
173 return;
174 const auto count = std::min(size - 1, source.size());
175 std::memcpy(destination, source.data(), count);
176 destination[count] = '\0';
177}
178
179void logAction(const Policy& policy, const fs::path& path,
180 const std::uint32_t eventType,
181 const std::uint32_t verdict,
182 const struct stat* before = nullptr,
183 const struct stat* after = nullptr)
184{
185 if (policy.eventLog.empty())
186 return;
187 try {
188 if (const auto parent = policy.eventLog.parent_path(); !parent.empty())
189 fs::create_directories(parent);
190 JsonEventSink sink(policy.eventLog);
191 rg_event event{};
192 event.pid = static_cast<__u32>(::getpid());
193 event.tgid = event.pid;
194 event.ppid = static_cast<__u32>(::getppid());
195 event.event_type = eventType;
196 event.verdict = verdict;
197 if (before) {
198 event.file.device = FileIdentityResolver::kernelDeviceNumber(before->st_dev);
199 event.file.inode = static_cast<__u64>(before->st_ino);
200 event.old_mode = static_cast<__u32>(before->st_mode & 07777U);
201 event.old_uid = static_cast<__u32>(before->st_uid);
202 event.old_gid = static_cast<__u32>(before->st_gid);
203 }
204 if (after) {
205 event.new_mode = static_cast<__u32>(after->st_mode & 07777U);
206 event.new_uid = static_cast<__u32>(after->st_uid);
207 event.new_gid = static_cast<__u32>(after->st_gid);
208 }
209 event.path_resolution = RG_PATH_PROTECTED_BASELINE;
210 copyString(event.comm, sizeof(event.comm), "rootguard-action");
211 std::error_code actorError;
212 const fs::path actor = fs::read_symlink("/proc/self/exe", actorError);
213 if (!actorError)
214 copyString(event.actor_executable,
215 sizeof(event.actor_executable), actor.string());
216 copyString(event.filename, sizeof(event.filename), path.string());
217 sink.onEvent(event);
218 } catch (...) {
219 // The privileged action must not fail only because logging failed.
220 }
221}
222
223Policy loadPolicy(const fs::path& path)
224{
225 return PolicyLoader::loadFromText(PolicySecurity::read(path, true), path);
226}
227
228std::string trim(std::string value)
229{
230 const auto first = value.find_first_not_of(" \t\r\n");
231 if (first == std::string::npos)
232 return {};
233 const auto last = value.find_last_not_of(" \t\r\n");
234 return value.substr(first, last - first + 1);
235}
236
237void rewriteProtectionPolicy(const fs::path& policyPath, const bool enabled)
238{
239 struct stat metadata{};
240 if (::lstat(policyPath.c_str(), &metadata) != 0)
241 fail(std::string("Cannot stat policy: ") + std::strerror(errno));
242 if (!S_ISREG(metadata.st_mode) || S_ISLNK(metadata.st_mode))
243 fail("The RootGuard policy must be a regular non-symlink file.");
244
245 std::ifstream input(policyPath);
246 if (!input)
247 fail("Cannot read the RootGuard policy.");
248 std::vector<std::string> lines;
249 std::string line;
250 while (std::getline(input, line))
251 lines.push_back(line);
252
253 const std::map<std::string, std::string> replacements{
254 {"mode", enabled ? "enforce" : "audit"},
255 {"metadata_action", enabled ? "block" : "audit"},
256 {"global_metadata_action", "audit"},
257 {"block_user_home", "false"},
258 };
259 std::map<std::string, bool> found;
260 bool inEngine = false;
261 std::size_t insertion = lines.size();
262 for (std::size_t index = 0; index < lines.size(); ++index) {
263 const std::string stripped = trim(lines[index]);
264 if (!stripped.empty() && stripped.front() == '[' && stripped.back() == ']') {
265 if (inEngine && stripped != "[engine]") {
266 insertion = index;
267 inEngine = false;
268 } else {
269 inEngine = stripped == "[engine]";
270 }
271 continue;
272 }
273 if (!inEngine || stripped.empty() || stripped.front() == '#' || stripped.front() == ';')
274 continue;
275 const auto separator = stripped.find('=');
276 if (separator == std::string::npos)
277 continue;
278 const std::string key = trim(stripped.substr(0, separator));
279 const auto replacement = replacements.find(key);
280 if (replacement != replacements.end()) {
281 lines[index] = key + " = " + replacement->second;
282 found[key] = true;
283 }
284 }
285 if (insertion == lines.size() && inEngine)
286 insertion = lines.size();
287 for (auto iterator = replacements.rbegin(); iterator != replacements.rend(); ++iterator) {
288 if (!found[iterator->first])
289 lines.insert(lines.begin() + static_cast<std::ptrdiff_t>(insertion),
290 iterator->first + " = " + iterator->second);
291 }
292
293 const fs::path temporary = policyPath.string() + ".rootguard.tmp." +
294 std::to_string(::getpid());
295 {
296 std::ofstream output(temporary, std::ios::trunc);
297 if (!output)
298 fail("Cannot create the temporary policy file.");
299 for (const auto& item : lines)
300 output << item << '\n';
301 output.flush();
302 if (!output)
303 fail("Cannot write the temporary policy file.");
304 }
305 if (::chmod(temporary.c_str(), metadata.st_mode & 07777U) != 0 ||
306 ::chown(temporary.c_str(), metadata.st_uid, metadata.st_gid) != 0) {
307 fs::remove(temporary);
308 fail(std::string("Cannot preserve policy ownership/mode: ") + std::strerror(errno));
309 }
310 if (::rename(temporary.c_str(), policyPath.c_str()) != 0) {
311 fs::remove(temporary);
312 fail(std::string("Cannot atomically replace the policy: ") + std::strerror(errno));
313 }
314}
315
316std::vector<fs::path> normalizeApplicationEntries(
317 const std::vector<fs::path>& entries,
318 const char* label)
319{
320 std::vector<fs::path> normalized;
321 std::set<std::string> seen;
322 for (const auto& entry : entries) {
323 const fs::path path = normalizedAbsolute(entry);
324 struct stat metadata {};
325 const bool exists = ::stat(path.c_str(), &metadata) == 0;
326 if (!exists && errno != ENOENT)
327 fail(std::string("Cannot inspect ") + label + ": " + path.string() +
328 ": " + std::strerror(errno));
329 if (exists && !S_ISREG(metadata.st_mode))
330 fail(std::string(label) + " is not a regular file: " + path.string());
331 if (exists && ::access(path.c_str(), X_OK) != 0)
332 fail(std::string(label) + " is not executable: " + path.string());
333
334 /*
335 * Cross-distribution policy files legitimately contain executable
336 * paths that are absent on the current desktop. Preserve those entries
337 * so saving from the UI does not silently delete rules for another DE
338 * or browser. Existing files are still validated strictly.
339 */
340 std::error_code error;
341 const fs::path canonical = fs::weakly_canonical(path, error);
342 const fs::path finalPath = error ? path : canonical;
343 if (seen.insert(finalPath.string()).second)
344 normalized.push_back(finalPath);
345 }
346 std::sort(normalized.begin(), normalized.end());
347 return normalized;
348}
349
350void writePolicyAtomically(const fs::path& policyPath,
351 const std::vector<std::string>& lines,
352 const struct stat& metadata)
353{
354 const fs::path temporary = policyPath.string() + ".rootguard.tmp." +
355 std::to_string(::getpid());
356 {
357 std::ofstream output(temporary, std::ios::trunc);
358 if (!output)
359 fail("Cannot create the temporary policy file.");
360 for (const auto& item : lines)
361 output << item << '\n';
362 output.flush();
363 if (!output)
364 fail("Cannot write the temporary policy file.");
365 }
366
367 std::ifstream validationInput(temporary);
368 std::ostringstream validationText;
369 validationText << validationInput.rdbuf();
370 try {
371 (void)PolicyLoader::loadFromText(validationText.str(), temporary);
372 } catch (const std::exception& error) {
373 fs::remove(temporary);
374 fail(std::string("Refusing to install an invalid RootGuard policy: ") +
375 error.what());
376 }
377
378 if (::chmod(temporary.c_str(), metadata.st_mode & 07777U) != 0 ||
379 ::chown(temporary.c_str(), metadata.st_uid, metadata.st_gid) != 0) {
380 fs::remove(temporary);
381 fail(std::string("Cannot preserve policy ownership/mode: ") +
382 std::strerror(errno));
383 }
384 if (::rename(temporary.c_str(), policyPath.c_str()) != 0) {
385 fs::remove(temporary);
386 fail(std::string("Cannot atomically replace the policy: ") +
387 std::strerror(errno));
388 }
389}
390
392{
393 struct stat metadata {};
394 if (::lstat(options.policy.c_str(), &metadata) != 0)
395 fail(std::string("Cannot stat policy: ") + std::strerror(errno));
396 if (!S_ISREG(metadata.st_mode) || S_ISLNK(metadata.st_mode))
397 fail("The RootGuard policy must be a regular non-symlink file.");
398
399 const auto trustedApplications = normalizeApplicationEntries(
400 options.trustedApplications, "Trusted application");
401 const auto trustedExecutables = normalizeApplicationEntries(
402 options.trustedExecutables, "Privilege-trusted executable");
403 const auto deniedExecutables = normalizeApplicationEntries(
404 options.deniedExecutables, "Denied executable");
405
406 std::set<std::string> trustedKeys;
407 for (const auto& path : trustedApplications)
408 trustedKeys.insert(path.string());
409 for (const auto& path : trustedExecutables)
410 trustedKeys.insert(path.string());
411 for (const auto& path : deniedExecutables) {
412 if (trustedKeys.count(path.string()) != 0)
413 fail("An executable cannot be both trusted and denied: " + path.string());
414 }
415
416 std::ifstream input(options.policy);
417 if (!input)
418 fail("Cannot read the RootGuard policy.");
419 std::vector<std::string> sourceLines;
420 std::string line;
421 while (std::getline(input, line))
422 sourceLines.push_back(line);
423
424 const std::map<std::string, std::vector<fs::path>> replacements {
425 {"trusted-applications", trustedApplications},
426 {"trusted-executables", trustedExecutables},
427 {"denied-executables", deniedExecutables},
428 };
429 std::set<std::string> emitted;
430 std::vector<std::string> output;
431 std::string skippedSection;
432
433 const auto emitSection = [&](const std::string& section,
434 std::vector<std::string>& destination) {
435 destination.push_back("[" + section + "]");
436 if (section == "trusted-applications") {
437 destination.push_back(
438 "# Routine user-space metadata activity only. This never grants privilege trust.");
439 } else if (section == "trusted-executables") {
440 destination.push_back(
441 "# Executables trusted specifically for privilege transitions.");
442 } else {
443 destination.push_back(
444 "# Executables denied during RootGuard privilege-transition checks.");
445 }
446 for (const auto& path : replacements.at(section))
447 destination.push_back("path = " + path.string());
448 destination.push_back("");
449 };
450
451 for (const auto& sourceLine : sourceLines) {
452 const std::string stripped = trim(sourceLine);
453 if (!stripped.empty() && stripped.front() == '[' && stripped.back() == ']') {
454 const std::string section = stripped.substr(1, stripped.size() - 2);
455 skippedSection.clear();
456 if (replacements.count(section) != 0) {
457 skippedSection = section;
458 if (emitted.insert(section).second)
459 emitSection(section, output);
460 continue;
461 }
462 }
463 if (!skippedSection.empty())
464 continue;
465 output.push_back(sourceLine);
466 }
467
468 for (const auto& [section, entries] : replacements) {
469 (void)entries;
470 if (emitted.insert(section).second) {
471 if (!output.empty() && !output.back().empty())
472 output.push_back("");
473 emitSection(section, output);
474 }
475 }
476
477 writePolicyAtomically(options.policy, output, metadata);
478 std::cout << "RootGuard application rules saved. A service restart is required.\n";
479}
480
481void restoreFile(const Options& options, const Policy& policy)
482{
483 if (!options.mode || !options.uid || !options.gid)
484 fail("restore requires --mode, --uid and --gid.");
485 const auto path = normalizedAbsolute(options.path);
486 requireProtectedPath(policy, path);
487
488 struct stat before{};
489 if (::lstat(path.c_str(), &before) != 0)
490 fail(std::string("Cannot restore missing path: ") + std::strerror(errno));
491 if (S_ISLNK(before.st_mode))
492 fail("Refusing to restore metadata through a symbolic link.");
493 if (options.device && *options.device != 0 &&
494 FileIdentityResolver::kernelDeviceNumber(before.st_dev) != *options.device)
495 fail("The file device no longer matches the protected baseline.");
496 if (options.inode && *options.inode != 0 &&
497 static_cast<std::uint64_t>(before.st_ino) != *options.inode)
498 fail("The file inode no longer matches the protected baseline. Quarantine the replacement instead.");
499
500 const int openFlags = O_RDONLY | O_CLOEXEC | O_NOFOLLOW |
501 (S_ISDIR(before.st_mode) ? O_DIRECTORY : O_NONBLOCK);
502 const int descriptor = ::open(path.c_str(), openFlags);
503 if (descriptor < 0)
504 fail(std::string("Secure open failed: ") + std::strerror(errno));
505 struct stat verified{};
506 if (::fstat(descriptor, &verified) != 0 ||
507 verified.st_dev != before.st_dev || verified.st_ino != before.st_ino) {
508 ::close(descriptor);
509 fail("The protected path changed during the restore operation.");
510 }
511 if (::fchown(descriptor, static_cast<uid_t>(*options.uid),
512 static_cast<gid_t>(*options.gid)) != 0) {
513 const std::string error = std::strerror(errno);
514 ::close(descriptor);
515 fail("fchown failed: " + error);
516 }
517 if (::fchmod(descriptor, static_cast<mode_t>(*options.mode & 07777U)) != 0) {
518 const std::string error = std::strerror(errno);
519 ::close(descriptor);
520 fail("fchmod failed: " + error);
521 }
522 ::close(descriptor);
523
524 const auto serviceResult = unblockSystemService(path);
525 (void)serviceResult;
526 struct stat after{};
527 ::lstat(path.c_str(), &after);
529 &before, &after);
530 std::cout << "Protected metadata restored. A system service, when applicable, was unmasked but not restarted.\n";
531}
532
533void quarantineFile(const Options& options, const Policy& policy)
534{
535 const auto path = normalizedAbsolute(options.path);
536 requireProtectedPath(policy, path);
537 struct stat before{};
538 if (::lstat(path.c_str(), &before) != 0)
539 fail(std::string("Cannot remove path: ") + std::strerror(errno));
540 if (S_ISDIR(before.st_mode))
541 fail("Directory removal is not supported by the RootGuard incident action.");
542
543 std::string serviceWarning;
544 if (const auto target = identifySystemService(path)) {
545 const bool identityMatches = options.device && options.inode &&
546 *options.device != 0 && *options.inode != 0 &&
547 FileIdentityResolver::kernelDeviceNumber(before.st_dev) == *options.device &&
548 static_cast<std::uint64_t>(before.st_ino) == *options.inode;
549 const bool scriptBased = target->init == InitSystem::OpenRC ||
550 target->init == InitSystem::SysVInit;
551 if (scriptBased && !identityMatches) {
552 serviceWarning =
553 "The affected OpenRC/SysV definition may have been replaced; "
554 "RootGuard did not execute it as root. The service process may require manual containment.";
555 } else {
556 const auto result = blockSystemService(path);
557 if (!result.success)
558 serviceWarning = "Service blocking was not confirmed: " + result.message;
559 }
560 }
561
562 fs::create_directories(kQuarantineDir);
563 ::chmod(kQuarantineDir, 0700);
564 const fs::path destination = fs::path(kQuarantineDir) /
565 (timestamp() + "-" + std::to_string(::getpid()) + "-" + path.filename().string());
566 if (::rename(path.c_str(), destination.c_str()) != 0)
567 fail(std::string("Quarantine rename failed: ") + std::strerror(errno));
568
569 std::ofstream manifest(destination.string() + ".manifest", std::ios::trunc);
570 manifest << "original_path=" << path.string() << '\n'
571 << "mode=" << std::oct << (before.st_mode & 07777U) << std::dec << '\n'
572 << "uid=" << before.st_uid << '\n'
573 << "gid=" << before.st_gid << '\n'
574 << "device=" << FileIdentityResolver::kernelDeviceNumber(before.st_dev) << '\n'
575 << "inode=" << before.st_ino << '\n';
576 manifest.close();
577 ::chmod(destination.c_str(), 0000);
579 &before, nullptr);
580 std::cout << "The file was removed from its original location and quarantined at "
581 << destination << ".\n";
582}
583
584} // namespace
585
586int main(const int argc, char* argv[])
587{
588 try {
589 if (::geteuid() != 0)
590 fail("Administrative privileges are required.");
591 const Options options = parseOptions(argc, argv);
592 const Policy policy = loadPolicy(options.policy);
593
594 if (options.command == "set-protection") {
595 if (options.protection != "on" && options.protection != "off")
596 fail("set-protection requires --state on|off.");
597 const bool enabled = options.protection == "on";
598 rewriteProtectionPolicy(options.policy, enabled);
599 const Policy updated = loadPolicy(options.policy);
600 logAction(updated, options.policy, RG_EVENT_PROTECTION_MODE_CHANGED,
602 std::cout << (enabled
603 ? "Immediate blocking enabled for protected system paths; user home remains audit-only."
604 : "Immediate metadata blocking disabled.") << '\n';
605 } else if (options.command == "set-application-rules") {
607 } else if (options.command == "restore") {
608 restoreFile(options, policy);
609 } else if (options.command == "remove") {
610 quarantineFile(options, policy);
611 } else if (options.command == "block-service") {
612 const auto path = normalizedAbsolute(options.path);
613 requireProtectedPath(policy, path);
614 if (const auto target = identifySystemService(path)) {
615 if (target->init == InitSystem::OpenRC || target->init == InitSystem::SysVInit) {
616 struct stat current{};
617 if (!options.device || !options.inode ||
618 ::lstat(path.c_str(), &current) != 0 ||
619 FileIdentityResolver::kernelDeviceNumber(current.st_dev) != *options.device ||
620 static_cast<std::uint64_t>(current.st_ino) != *options.inode) {
621 fail("Refusing to execute an OpenRC/SysV service definition without a matching protected identity.");
622 }
623 }
624 }
625 const auto result = blockSystemService(path);
626 if (!result.success) fail(result.message);
628 std::cout << result.message << '\n';
629 } else if (options.command == "unblock-service") {
630 const auto path = normalizedAbsolute(options.path);
631 requireProtectedPath(policy, path);
632 const auto result = unblockSystemService(path);
633 if (!result.success) fail(result.message);
634 std::cout << result.message << '\n';
635 } else {
636 fail("Unknown action: " + options.command);
637 }
638 return 0;
639 } catch (const std::exception& error) {
640 std::cerr << "RootGuard action: " << error.what() << '\n';
641 return 1;
642 }
643}
int main(int argc, char *argv[])
static __u64 kernelDeviceNumber(dev_t encodedDevice)
void onEvent(const rg_event &event) noexcept override
static Policy loadFromText(const std::string &content, const std::filesystem::path &source="<memory>")
static std::string read(const std::filesystem::path &path, bool requireSecureOwnership)
std::uint64_t parseUnsigned(const std::string &value, const char *label, const int base=10)
Policy loadPolicy(const fs::path &path)
std::string trim(std::string value)
std::vector< fs::path > normalizeApplicationEntries(const std::vector< fs::path > &entries, const char *label)
void fail(const std::string &message)
Options parseOptions(const int argc, char *argv[])
constexpr const char * kQuarantineDir
void requireProtectedPath(const Policy &policy, const fs::path &path)
fs::path normalizedAbsolute(const fs::path &path)
void quarantineFile(const Options &options, const Policy &policy)
void rewriteApplicationRules(const Options &options)
void logAction(const Policy &policy, const fs::path &path, const std::uint32_t eventType, const std::uint32_t verdict, const struct stat *before=nullptr, const struct stat *after=nullptr)
bool isProtectedPath(const Policy &policy, const fs::path &requested)
constexpr const char * kDefaultPolicy
bool isIgnoredPath(const Policy &policy, const fs::path &requested)
void rewriteProtectionPolicy(const fs::path &policyPath, const bool enabled)
void copyString(char *destination, const std::size_t size, const std::string &source)
bool pathStartsWith(const fs::path &path, const fs::path &prefix)
void restoreFile(const Options &options, const Policy &policy)
void writePolicyAtomically(const fs::path &policyPath, const std::vector< std::string > &lines, const struct stat &metadata)
std::optional< ServiceTarget > identifySystemService(const std::filesystem::path &path) noexcept
ServiceActionResult blockSystemService(const std::filesystem::path &path) noexcept
ServiceActionResult unblockSystemService(const std::filesystem::path &path) noexcept
@ RG_EVENT_FILE_QUARANTINED
@ RG_EVENT_SERVICE_BLOCKED
@ RG_EVENT_PROTECTION_MODE_CHANGED
@ RG_EVENT_FILE_RESTORED
@ RG_PATH_PROTECTED_BASELINE
@ RG_VERDICT_AUDIT
@ RG_VERDICT_RESTORED
@ RG_VERDICT_BLOCKED
std::vector< PolicyPath > ignoredPaths
Definition Policy.hpp:59
std::filesystem::path eventLog
Definition Policy.hpp:54
std::vector< PolicyPath > protectedPaths
Definition Policy.hpp:58