Skip to content
API Documentation
BastionGuard™
anonymous_namespace{ActionHelper.cpp} Namespace Reference

Classes

struct  Options

Functions

void fail (const std::string &message)
std::uint64_t parseUnsigned (const std::string &value, const char *label, const int base=10)
Options parseOptions (const int argc, char *argv[])
bool pathStartsWith (const fs::path &path, const fs::path &prefix)
fs::path normalizedAbsolute (const fs::path &path)
bool isIgnoredPath (const Policy &policy, const fs::path &requested)
bool isProtectedPath (const Policy &policy, const fs::path &requested)
void requireProtectedPath (const Policy &policy, const fs::path &path)
std::string timestamp ()
void copyString (char *destination, const std::size_t size, const std::string &source)
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)
Policy loadPolicy (const fs::path &path)
std::string trim (std::string value)
void rewriteProtectionPolicy (const fs::path &policyPath, const bool enabled)
std::vector< fs::path > normalizeApplicationEntries (const std::vector< fs::path > &entries, const char *label)
void writePolicyAtomically (const fs::path &policyPath, const std::vector< std::string > &lines, const struct stat &metadata)
void rewriteApplicationRules (const Options &options)
void restoreFile (const Options &options, const Policy &policy)
void quarantineFile (const Options &options, const Policy &policy)

Variables

constexpr const char * kDefaultPolicy = "/etc/bastionguard/rootguard.conf"
constexpr const char * kQuarantineDir = "/var/lib/bastionguard/rootguard/quarantine"

Function Documentation

◆ copyString()

void anonymous_namespace{ActionHelper.cpp}::copyString ( char * destination,
const std::size_t size,
const std::string & source )

Definition at line 169 of file ActionHelper.cpp.

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}

References copyString().

Referenced by copyString(), and logAction().

◆ fail()

void anonymous_namespace{ActionHelper.cpp}::fail ( const std::string & message)

◆ isIgnoredPath()

bool anonymous_namespace{ActionHelper.cpp}::isIgnoredPath ( const Policy & policy,
const fs::path & requested )

Definition at line 125 of file ActionHelper.cpp.

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}
fs::path normalizedAbsolute(const fs::path &path)
bool pathStartsWith(const fs::path &path, const fs::path &prefix)
std::vector< PolicyPath > ignoredPaths
Definition Policy.hpp:59

References bastionguard::rootguard::Policy::ignoredPaths, isIgnoredPath(), normalizedAbsolute(), and pathStartsWith().

Referenced by isIgnoredPath(), and isProtectedPath().

◆ isProtectedPath()

bool anonymous_namespace{ActionHelper.cpp}::isProtectedPath ( const Policy & policy,
const fs::path & requested )

Definition at line 137 of file ActionHelper.cpp.

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}
bool isIgnoredPath(const Policy &policy, const fs::path &requested)
std::vector< PolicyPath > protectedPaths
Definition Policy.hpp:58

References isIgnoredPath(), isProtectedPath(), normalizedAbsolute(), pathStartsWith(), and bastionguard::rootguard::Policy::protectedPaths.

Referenced by isProtectedPath(), and requireProtectedPath().

◆ loadPolicy()

Policy anonymous_namespace{ActionHelper.cpp}::loadPolicy ( const fs::path & path)

Definition at line 223 of file ActionHelper.cpp.

224{
225 return PolicyLoader::loadFromText(PolicySecurity::read(path, true), path);
226}
static Policy loadFromText(const std::string &content, const std::filesystem::path &source="<memory>")
static std::string read(const std::filesystem::path &path, bool requireSecureOwnership)

References bastionguard::rootguard::PolicyLoader::loadFromText(), loadPolicy(), and bastionguard::rootguard::PolicySecurity::read().

Referenced by loadPolicy(), and main().

◆ logAction()

void anonymous_namespace{ActionHelper.cpp}::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 )

Definition at line 179 of file ActionHelper.cpp.

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}
static __u64 kernelDeviceNumber(dev_t encodedDevice)
void copyString(char *destination, const std::size_t size, const std::string &source)
@ RG_PATH_PROTECTED_BASELINE
std::filesystem::path eventLog
Definition Policy.hpp:54

References copyString(), bastionguard::rootguard::Policy::eventLog, bastionguard::rootguard::FileIdentityResolver::kernelDeviceNumber(), logAction(), bastionguard::rootguard::JsonEventSink::onEvent(), rg_event::pid, and RG_PATH_PROTECTED_BASELINE.

Referenced by logAction(), main(), quarantineFile(), and restoreFile().

◆ normalizeApplicationEntries()

std::vector< fs::path > anonymous_namespace{ActionHelper.cpp}::normalizeApplicationEntries ( const std::vector< fs::path > & entries,
const char * label )

Definition at line 316 of file ActionHelper.cpp.

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}
void fail(const std::string &message)

References fail(), normalizeApplicationEntries(), and normalizedAbsolute().

Referenced by normalizeApplicationEntries(), and rewriteApplicationRules().

◆ normalizedAbsolute()

fs::path anonymous_namespace{ActionHelper.cpp}::normalizedAbsolute ( const fs::path & path)

Definition at line 113 of file ActionHelper.cpp.

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}

References fail(), and normalizedAbsolute().

Referenced by isIgnoredPath(), isProtectedPath(), main(), normalizeApplicationEntries(), normalizedAbsolute(), quarantineFile(), and restoreFile().

◆ parseOptions()

Options anonymous_namespace{ActionHelper.cpp}::parseOptions ( const int argc,
char * argv[] )

Definition at line 72 of file ActionHelper.cpp.

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}
std::uint64_t parseUnsigned(const std::string &value, const char *label, const int base=10)

References anonymous_namespace{ActionHelper.cpp}::Options::command, anonymous_namespace{ActionHelper.cpp}::Options::deniedExecutables, anonymous_namespace{ActionHelper.cpp}::Options::device, fail(), anonymous_namespace{ActionHelper.cpp}::Options::gid, anonymous_namespace{ActionHelper.cpp}::Options::inode, anonymous_namespace{ActionHelper.cpp}::Options::mode, parseOptions(), parseUnsigned(), anonymous_namespace{ActionHelper.cpp}::Options::path, anonymous_namespace{ActionHelper.cpp}::Options::policy, anonymous_namespace{ActionHelper.cpp}::Options::protection, anonymous_namespace{ActionHelper.cpp}::Options::trustedApplications, anonymous_namespace{ActionHelper.cpp}::Options::trustedExecutables, and anonymous_namespace{ActionHelper.cpp}::Options::uid.

Referenced by main(), and parseOptions().

◆ parseUnsigned()

std::uint64_t anonymous_namespace{ActionHelper.cpp}::parseUnsigned ( const std::string & value,
const char * label,
const int base = 10 )

Definition at line 57 of file ActionHelper.cpp.

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}

References fail(), and parseUnsigned().

Referenced by parseOptions(), and parseUnsigned().

◆ pathStartsWith()

bool anonymous_namespace{ActionHelper.cpp}::pathStartsWith ( const fs::path & path,
const fs::path & prefix )

Definition at line 100 of file ActionHelper.cpp.

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}

References pathStartsWith().

Referenced by isIgnoredPath(), isProtectedPath(), and pathStartsWith().

◆ quarantineFile()

void anonymous_namespace{ActionHelper.cpp}::quarantineFile ( const Options & options,
const Policy & policy )

Definition at line 533 of file ActionHelper.cpp.

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}
constexpr const char * kQuarantineDir
void requireProtectedPath(const Policy &policy, const fs::path &path)
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)
std::optional< ServiceTarget > identifySystemService(const std::filesystem::path &path) noexcept
ServiceActionResult blockSystemService(const std::filesystem::path &path) noexcept
@ RG_EVENT_FILE_QUARANTINED
@ RG_VERDICT_BLOCKED

References bastionguard::rootguard::blockSystemService(), anonymous_namespace{ActionHelper.cpp}::Options::device, fail(), bastionguard::rootguard::identifySystemService(), anonymous_namespace{ActionHelper.cpp}::Options::inode, bastionguard::rootguard::FileIdentityResolver::kernelDeviceNumber(), kQuarantineDir, logAction(), normalizedAbsolute(), bastionguard::rootguard::OpenRC, anonymous_namespace{ActionHelper.cpp}::Options::path, quarantineFile(), requireProtectedPath(), RG_EVENT_FILE_QUARANTINED, RG_VERDICT_BLOCKED, bastionguard::rootguard::SysVInit, and timestamp().

Referenced by main(), and quarantineFile().

◆ requireProtectedPath()

void anonymous_namespace{ActionHelper.cpp}::requireProtectedPath ( const Policy & policy,
const fs::path & path )

Definition at line 152 of file ActionHelper.cpp.

153{
154 if (!isProtectedPath(policy, path))
155 fail("The requested path is not covered by the RootGuard protected-path policy.");
156}
bool isProtectedPath(const Policy &policy, const fs::path &requested)

References fail(), isProtectedPath(), and requireProtectedPath().

Referenced by main(), quarantineFile(), requireProtectedPath(), and restoreFile().

◆ restoreFile()

void anonymous_namespace{ActionHelper.cpp}::restoreFile ( const Options & options,
const Policy & policy )

Definition at line 481 of file ActionHelper.cpp.

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}
ServiceActionResult unblockSystemService(const std::filesystem::path &path) noexcept
@ RG_EVENT_FILE_RESTORED
@ RG_VERDICT_RESTORED

References anonymous_namespace{ActionHelper.cpp}::Options::device, fail(), anonymous_namespace{ActionHelper.cpp}::Options::gid, anonymous_namespace{ActionHelper.cpp}::Options::inode, bastionguard::rootguard::FileIdentityResolver::kernelDeviceNumber(), logAction(), anonymous_namespace{ActionHelper.cpp}::Options::mode, normalizedAbsolute(), anonymous_namespace{ActionHelper.cpp}::Options::path, requireProtectedPath(), restoreFile(), RG_EVENT_FILE_RESTORED, RG_VERDICT_RESTORED, anonymous_namespace{ActionHelper.cpp}::Options::uid, and bastionguard::rootguard::unblockSystemService().

Referenced by main(), and restoreFile().

◆ rewriteApplicationRules()

void anonymous_namespace{ActionHelper.cpp}::rewriteApplicationRules ( const Options & options)

Definition at line 391 of file ActionHelper.cpp.

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}
std::string trim(std::string value)
std::vector< fs::path > normalizeApplicationEntries(const std::vector< fs::path > &entries, const char *label)
void writePolicyAtomically(const fs::path &policyPath, const std::vector< std::string > &lines, const struct stat &metadata)

References anonymous_namespace{ActionHelper.cpp}::Options::deniedExecutables, fail(), normalizeApplicationEntries(), anonymous_namespace{ActionHelper.cpp}::Options::policy, rewriteApplicationRules(), trim(), anonymous_namespace{ActionHelper.cpp}::Options::trustedApplications, anonymous_namespace{ActionHelper.cpp}::Options::trustedExecutables, and writePolicyAtomically().

Referenced by main(), and rewriteApplicationRules().

◆ rewriteProtectionPolicy()

void anonymous_namespace{ActionHelper.cpp}::rewriteProtectionPolicy ( const fs::path & policyPath,
const bool enabled )

Definition at line 237 of file ActionHelper.cpp.

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}

References fail(), rewriteProtectionPolicy(), and trim().

Referenced by main(), and rewriteProtectionPolicy().

◆ timestamp()

std::string anonymous_namespace{ActionHelper.cpp}::timestamp ( )

Definition at line 158 of file ActionHelper.cpp.

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}

References timestamp().

Referenced by quarantineFile(), and timestamp().

◆ trim()

std::string anonymous_namespace{ActionHelper.cpp}::trim ( std::string value)

Definition at line 228 of file ActionHelper.cpp.

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}

References trim().

Referenced by rewriteApplicationRules(), rewriteProtectionPolicy(), and trim().

◆ writePolicyAtomically()

void anonymous_namespace{ActionHelper.cpp}::writePolicyAtomically ( const fs::path & policyPath,
const std::vector< std::string > & lines,
const struct stat & metadata )

Definition at line 350 of file ActionHelper.cpp.

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}

References fail(), bastionguard::rootguard::PolicyLoader::loadFromText(), and writePolicyAtomically().

Referenced by rewriteApplicationRules(), and writePolicyAtomically().

Variable Documentation

◆ kDefaultPolicy

const char* anonymous_namespace{ActionHelper.cpp}::kDefaultPolicy = "/etc/bastionguard/rootguard.conf"
constexpr

Definition at line 34 of file ActionHelper.cpp.

◆ kQuarantineDir

const char* anonymous_namespace{ActionHelper.cpp}::kQuarantineDir = "/var/lib/bastionguard/rootguard/quarantine"
constexpr

Definition at line 35 of file ActionHelper.cpp.

Referenced by quarantineFile().