AppArmor profile per binary path with install hint; verified network examples

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 22:08:37 +02:00
co-authored by Claude Sonnet 5.5
parent 530582e826
commit c3ebf21e9c
11 changed files with 133 additions and 7 deletions
+49
View File
@@ -15,6 +15,7 @@
#include <unistd.h>
#include <cstring>
#include <fstream>
#include <poll.h>
#include <algorithm>
@@ -293,12 +294,60 @@ int containerInit(void* arg) {
::_exit(code);
} catch (const std::exception& e) {
::dprintf(2, "doos: %s\n", e.what());
auto* se = dynamic_cast<const SysError*>(&e);
std::ifstream restrictUserns("/proc/sys/kernel/apparmor_restrict_unprivileged_userns");
int restricted = 0;
if (restrictUserns) restrictUserns >> restricted;
if (se && (se->code() == EPERM || se->code() == EACCES) && restricted == 1) {
::dprintf(2, "doos: AppArmor restricts user namespaces for this binary. Install a profile once with:\n %s\n",
Sandbox::apparmorInstallHint().c_str());
}
::_exit(125);
}
}
} // namespace
namespace {
fs::path selfPath() {
std::error_code ec;
fs::path self = fs::read_symlink("/proc/self/exe", ec);
if (ec) throw std::runtime_error("cannot resolve the path of this binary: " + ec.message());
std::string path = self.string();
if (path.find_first_of("{}[]*?\"\n,") != std::string::npos) {
throw std::runtime_error("binary path contains characters AppArmor treats as globs: " + path);
}
return self;
}
// One profile per binary path, so installing doos in a second place does not replace the first profile.
std::string profileName(const fs::path& self) {
std::string n = "doos" + self.string();
for (auto& c : n) {
if (c == '/') c = '-';
}
return n;
}
} // namespace
std::string Sandbox::apparmorProfile() {
fs::path self = selfPath();
return "# Lets doos use unprivileged user namespaces where kernel.apparmor_restrict_unprivileged_userns=1.\n"
"# Generated by: doos apparmor (profile is for exactly this path)\n"
"abi <abi/5.0>,\n"
"include <tunables/global>\n"
"\n"
"profile " + profileName(self) + " " + self.string() + " flags=(unconfined) {\n"
" userns,\n"
" @{exec_path} mr,\n"
"}\n";
}
std::string Sandbox::apparmorInstallHint() {
std::string file = "/etc/apparmor.d/" + profileName(selfPath());
return "doos apparmor | sudo tee " + file + " >/dev/null && sudo apparmor_parser -r " + file;
}
int Sandbox::run() {
if (command_.size() > spec::kMaxCommandArgs) throw std::runtime_error("too many command arguments");
// Decide start-or-join under the lock so concurrent attaches agree.