Files
doos/audit/CVE-2021-30465

CVE-2021-30465: runc symlink-exchange race during mount setup ("symlink-exchange attack")

Scope: audit · README · COMPARISON · docs/RESEARCH

CVSS 8.5. Fixed in runc 1.0.0-rc95.

Source

Relevance to doos

runc resolved a mount target, then mounted it, with a window between the two where an attacker sharing a volume could swap the target for a symlink, redirecting the mount outside the container's intended root.

doos builds its own container directories with fsutil::mkdirs, which calls std::filesystem::create_directories. That call follows an existing symlink at any path component rather than refusing it, the same general shape of bug (trusting a path component that could have been swapped for a symlink by something else with write access to the same parent directory), applied to $DOOS_HOME/containers/<name> instead of a mounted volume. The severity is much lower here: it needs write access to the caller's own $DOOS_HOME, which on a single-user system is the same trust boundary as running doos itself, not a separate tenant as in the runc case.

What the check does

Plants a symlink at the exact path get_or_create would otherwise create ($DOOS_HOME/containers/<name>), pointing at a sentinel directory outside $DOOS_HOME. Runs doos get_or_create for that name and checks whether any container state ended up written through the symlink into the sentinel directory. Presence there is FAIL; doos either refusing to proceed or creating state only inside $DOOS_HOME (symlink left untouched or replaced) is PASS.

Caveats

This is a same-user, same-$DOOS_HOME scenario, not a multi-tenant one: on a normal single-user install nothing else has write access to plant the symlink in the first place, which is why this is a defense-in-depth check and not a finding about a realistic attacker in doos's actual (single-user) deployment model.

Run: make test in this directory, or make -C audit test from the repo root.