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
+7
View File
@@ -112,6 +112,7 @@ void Cli::usage(std::FILE* out) {
" doos image list\n"
" doos image add [--sha256 HEX] SOURCE [NAME]\n"
" doos image delete [--yes] NAME\n"
" doos apparmor print the AppArmor profile this binary needs on Ubuntu 24.04+\n"
"\n"
"options:\n"
" --image NAME start from an image instead of the host system (fetched on demand) [fixed]\n"
@@ -144,6 +145,7 @@ int Cli::run() {
if (cmd == "attach") return cmdAttach(rest);
if (cmd == "delete") return cmdDelete(rest);
if (cmd == "image") return cmdImage(rest);
if (cmd == "apparmor") return cmdApparmor();
std::fprintf(stderr, "doos: unknown command '%s'\n\n", args_[0].c_str());
usage(stderr);
return 2;
@@ -187,6 +189,11 @@ int Cli::cmdDelete(std::vector<std::string> rest) {
return 0;
}
int Cli::cmdApparmor() {
std::fputs(Sandbox::apparmorProfile().c_str(), stdout);
return 0;
}
int Cli::cmdImage(std::vector<std::string> rest) {
if (rest.empty()) throw std::runtime_error("image needs a subcommand: list, add, delete");
std::string sub = rest[0];
+1
View File
@@ -15,6 +15,7 @@ private:
int cmdGetOrCreate(std::vector<std::string> rest);
int cmdAttach(std::vector<std::string> rest);
int cmdDelete(std::vector<std::string> rest);
int cmdApparmor();
int cmdImage(std::vector<std::string> rest);
int cmdImageList();
int cmdImageAdd(std::vector<std::string> rest);
+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.
+5
View File
@@ -18,6 +18,11 @@ public:
int run();
// AppArmor profile (text) that lets this very binary use user namespaces on Ubuntu 24.04+.
static std::string apparmorProfile();
// Shell command that installs it; shown when a start fails because of AppArmor.
static std::string apparmorInstallHint();
private:
int start(FileLock& lock);
int watch(pid_t child, Cgroup* cgroup);