Files
doos/valgrind

valgrind

Scope: README · AGENTS · docs/RESEARCH

Memcheck run against a fixed, named set of doos invocations, to catch real memory bugs (invalid reads/writes, use-after-free, definite leaks) in doos's own code: namespace setup, mount handling, the CLI, the image store. Separate from audit/ (named CVEs against other software) and from tests/run.sh (behavior, not memory safety).

Running it

make valgrind            # builds build/doos first, then runs scripts/valgrind.sh against it
scripts/valgrind.sh PATH  # run directly against a specific binary, e.g. a copy with an
                          # installed AppArmor profile (see README's apparmor section)

Reports land in build/valgrind-reports/<step>.log, one per invocation, kept (not deleted) after the run so a failure can be inspected. Each step prints ok or FAIL; the run exits non-zero only if valgrind itself flagged something, never because doos exited non-zero for its own reasons (an expected delete dry-run notice, an AppArmor block on a binary with no profile installed, and so on, all exit non-zero as designed and are not memory bugs).

What it checks

A fixed, capped list (scripts/valgrind.sh): list, get_or_create, attach (twice, to exercise both the create path and joining an already-running container), image list, delete --yes. All run with --net none and no image, so the check needs no network and no download; it is about doos's own allocation code, not about what happens after it execs the contained command.

Suppressions (doos.supp)

Six specific, named, commented allocation sites, each captured with valgrind --gen-suppressions=all against a real run and explained in the file itself: the clone(2) child's 1 MiB stack buffer (lifetime tied to the child process, not a scope), and five "an error path constructed an exception or opened a stream, then the namespace child exited right after" patterns (doMount's thrown SysError's message string and the exception object itself, once for a mount failure inside setupRoot and once for the mount that runs directly in containerInit before it, such as an AppArmor-blocked mount bind /; plus an ifstream opened in containerInit's cold path). None of these are "definitely lost"; valgrind calls all of them "possibly lost" because the process ends before the normal C++ unwind would run their destructors, which is expected for a namespace child that is about to disappear regardless. A genuinely new leak anywhere else is not covered by these exact stacks and still fails the check.

To add a suppression for a new, understood, one-time allocation: reproduce it, add --gen-suppressions=all to the valgrind invocation in scripts/valgrind.sh temporarily, copy the exact block doos.supp already prints into the file with a name and a comment explaining why it is not a real leak, then remove the temporary flag. Never add a suppression you cannot explain in one sentence.

What this does not cover

  • Anything that happens after doos execs the user's command: valgrind traces doos's own process tree, not an arbitrary program run inside the container.
  • Image download and extraction (--image): not in the fixed list, since it needs network and adds curl/tar/gpg subprocess noise that is not doos's own memory. Add a step to scripts/valgrind.sh following the same pattern if that code path needs a dedicated run.
  • slirp4netns/pasta: separate processes, not doos; see audit/CVE-2020-7039 for how their memory safety is tracked instead (upstream version, not valgrind).

Back to README, AGENTS, docs/RESEARCH.