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:
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user