doos 1.1.0: limits, hardening, pasta, signatures, Debian image
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+45
-10
@@ -17,10 +17,12 @@
|
||||
#include <cstring>
|
||||
|
||||
#include <poll.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "cgroup.hpp"
|
||||
#include "hardening.hpp"
|
||||
#include "spec.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
@@ -200,12 +202,16 @@ void applyLimitsAndEnv(const Container& c) {
|
||||
if (::prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) throw SysError("no_new_privs");
|
||||
rlimit core{0, 0};
|
||||
::setrlimit(RLIMIT_CORE, &core);
|
||||
// No single file can outgrow the size cap, which bounds the overshoot between two usage scans.
|
||||
rlimit fsize{c.sizeCap(), c.sizeCap()};
|
||||
::setrlimit(RLIMIT_FSIZE, &fsize);
|
||||
rlimit nofile;
|
||||
if (::getrlimit(RLIMIT_NOFILE, &nofile) == 0) {
|
||||
if (nofile.rlim_max > spec::kMaxOpenFiles) nofile.rlim_max = spec::kMaxOpenFiles;
|
||||
if (nofile.rlim_cur > nofile.rlim_max) nofile.rlim_cur = nofile.rlim_max;
|
||||
::setrlimit(RLIMIT_NOFILE, &nofile);
|
||||
}
|
||||
if (!c.relaxed()) Hardening::apply();
|
||||
const char* term = std::getenv("TERM");
|
||||
std::string termv = term && *term ? term : "xterm";
|
||||
const char* lang = std::getenv("LANG");
|
||||
@@ -340,12 +346,20 @@ int Sandbox::start(FileLock& lock) {
|
||||
IdMap::apply(child, container_.ids());
|
||||
|
||||
SlirpSession slirp;
|
||||
PastaSession pasta;
|
||||
if (net == NetMode::Slirp) slirp.start(child);
|
||||
if (net == NetMode::Pasta) pasta.start(child);
|
||||
|
||||
std::string cgroupWhy;
|
||||
std::unique_ptr<Cgroup> cgroup = Cgroup::create(container_.name(), cgroupWhy);
|
||||
if (cgroup) cgroup->add(child);
|
||||
else std::fprintf(stderr, "doos: size cap only warns, cannot freeze: %s\n", cgroupWhy.c_str());
|
||||
Limits limits{container_.memoryCap(), container_.pidsCap(), container_.milliCpus()};
|
||||
std::string cgroupWhy, notEnforced;
|
||||
std::unique_ptr<Cgroup> cgroup = Cgroup::create(container_.name(), limits, cgroupWhy, notEnforced);
|
||||
if (cgroup) {
|
||||
cgroup->addMain(child);
|
||||
if (!notEnforced.empty()) std::fprintf(stderr, "doos: limits not enforced (controller missing): %s\n", notEnforced.c_str());
|
||||
} else {
|
||||
std::fprintf(stderr, "doos: memory, pids and cpu limits are not enforced and the size cap only warns: %s\n",
|
||||
cgroupWhy.c_str());
|
||||
}
|
||||
|
||||
container_.recordPid(child);
|
||||
struct PidGuard {
|
||||
@@ -369,34 +383,50 @@ int Sandbox::start(FileLock& lock) {
|
||||
|
||||
int status = watch(child, cgroup.get());
|
||||
reaper.armed = false;
|
||||
if (cgroup && cgroup->oomKills() > 0) {
|
||||
std::fprintf(stderr, "doos: '%s' was killed by its memory limit (%s); raise it with --memory\n",
|
||||
container_.name().c_str(), formatSize(container_.memoryCap()).c_str());
|
||||
}
|
||||
if (cgroup && cgroup->pidFailures() > 0) {
|
||||
std::fprintf(stderr, "doos: '%s' hit its process limit (%u) %llu times; raise it with --pids\n",
|
||||
container_.name().c_str(), container_.pidsCap(),
|
||||
static_cast<unsigned long long>(cgroup->pidFailures()));
|
||||
}
|
||||
return exitCodeOf(status);
|
||||
}
|
||||
|
||||
// Waits for the container's init while enforcing the size cap: warn at kSizeWarnPercent, freeze at the cap,
|
||||
// thaw again once usage drops below kSizeThawPercent. Nothing is ever deleted by this loop.
|
||||
int Sandbox::watch(pid_t child, Cgroup* cgroup) {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
long fd = ::syscall(SYS_pidfd_open, child, 0);
|
||||
Fd pidfd(static_cast<int>(fd));
|
||||
const std::uint64_t cap = container_.sizeCap();
|
||||
bool warned = false;
|
||||
auto lastScan = std::chrono::steady_clock::now();
|
||||
auto nextScan = Clock::now() + std::chrono::milliseconds(spec::kSizeCheckMinIntervalMs);
|
||||
for (;;) {
|
||||
auto left = std::chrono::duration_cast<std::chrono::milliseconds>(nextScan - Clock::now()).count();
|
||||
int timeout = static_cast<int>(std::max<long>(left, 50));
|
||||
if (pidfd.valid()) {
|
||||
pollfd p{pidfd.get(), POLLIN, 0};
|
||||
int r = ::poll(&p, 1, spec::kSizeCheckIntervalMs);
|
||||
int r = ::poll(&p, 1, timeout);
|
||||
if (r > 0) break;
|
||||
if (r < 0 && errno != EINTR) break;
|
||||
} else {
|
||||
int st = 0;
|
||||
if (::waitpid(child, &st, WNOHANG) == child) return st;
|
||||
::poll(nullptr, 0, 1000);
|
||||
::poll(nullptr, 0, std::min(timeout, 1000));
|
||||
}
|
||||
if (g_terminating && cgroup && cgroup->frozen()) cgroup->freeze(false); // a frozen init cannot see SIGTERM
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (now - lastScan < std::chrono::milliseconds(spec::kSizeCheckIntervalMs)) continue;
|
||||
lastScan = now;
|
||||
if (Clock::now() < nextScan) continue;
|
||||
|
||||
auto began = Clock::now();
|
||||
std::size_t skipped = 0;
|
||||
std::uint64_t used = container_.usedBytes(skipped);
|
||||
// The scan may use at most 1/kSizeScanDutyFactor of the time: big trees are scanned less often.
|
||||
auto took = Clock::now() - began;
|
||||
nextScan = Clock::now() + std::max(std::chrono::milliseconds(spec::kSizeCheckMinIntervalMs),
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(took) * spec::kSizeScanDutyFactor);
|
||||
const char* name = container_.name().c_str();
|
||||
if (used >= cap) {
|
||||
if (cgroup && !cgroup->frozen()) {
|
||||
@@ -444,10 +474,15 @@ int Sandbox::join(pid_t initPid, FileLock& lock) {
|
||||
if (!rootFd.valid()) throw SysError("open container root");
|
||||
|
||||
lock.unlock();
|
||||
// Later attaches live in the `shell` leaf: same limits as the container, but never frozen.
|
||||
std::unique_ptr<Cgroup> cgroup = Cgroup::open(container_.name());
|
||||
pid_t child = ::fork();
|
||||
if (child < 0) throw SysError("fork");
|
||||
if (child == 0) {
|
||||
try {
|
||||
if (cgroup) {
|
||||
try { cgroup->addShell(::getpid()); } catch (const std::exception&) { /* limits only; keep going */ }
|
||||
}
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (fds[i].valid() && ::setns(fds[i].get(), kinds[i]) != 0) {
|
||||
throw SysError(std::string("setns ") + names[i]);
|
||||
|
||||
Reference in New Issue
Block a user