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