Skip to content

Reject path traversal in mount destinations during jail staging - #292

Open
M0nd0R wants to merge 2 commits into
google:masterfrom
M0nd0R:fix/mount-dst-path-traversal
Open

Reject path traversal in mount destinations during jail staging#292
M0nd0R wants to merge 2 commits into
google:masterfrom
M0nd0R:fix/mount-dst-path-traversal

Conversation

@M0nd0R

@M0nd0R M0nd0R commented Jul 27, 2026

Copy link
Copy Markdown

Summary

Mount destinations are joined as newroot + "/" + dst and walked with mkdirat/openat without rejecting . / .. components (and without O_NOFOLLOW). An attacker who can influence mount dst via CLI, config, or prefix_dst_env can create directories, symlinks, or mounts outside the intended staging root on the host before pivot_root.

This violates the intended invariant that destinations stay under the jail staging tree.

Changes

  • Add util::isSafeContainmentPath() to reject ., .., and embedded NUL
  • Harden createDirRecursively() to refuse unsafe paths and use O_NOFOLLOW
  • Validate destinations in both legacy (mnt_legacy.cc) and new (mnt_newapi.cc) mount APIs
  • Fail closed on unsafe destinations (do not remap to jail root)
  • Add standalone regression test: tests/path_containment_test.cc

Test plan

  • g++ -std=c++20 -O1 -o path_containment_test tests/path_containment_test.cc && ./path_containment_testOK path_containment_test passed
  • Build nsjail and confirm normal mounts (-R, -B, -T with safe paths) still work
  • Confirm unsafe destinations are rejected, e.g. -T '/tmp/../../../../tmp/evil'

PoC (pre-patch behavior)

createDirRecursively("/staging/root/../../escaped/pwned/x") created directories outside the staging prefix by following .. via openat.

M0nd0R added 2 commits July 27, 2026 10:53
Mount destinations were joined as newroot+"/"+dst and walked with
mkdirat/openat without rejecting "."/".." components or following
symlinks safely. An attacker who can influence mount dst (CLI, config,
or prefix_dst_env) could create directories, symlinks, or mounts outside
the intended staging root on the host before pivot_root.

Add isSafeContainmentPath(), validate destinations in both legacy and
new mount APIs, use O_NOFOLLOW while walking parents, and include a
standalone regression test.
PR google#292 rejected '.'/'..' and added O_NOFOLLOW on the legacy
createDirRecursively walk, but mnt_newapi createDirAt still used
multi-component mkdirat(), which follows intermediate symlinks.

After a -s symlink mount pointing outside the staging root, later
mounts with destinations under that symlink (e.g. link/pwned) could
create directories or attach mounts on the host before pivot_root.

Walk destinations one component at a time with O_NOFOLLOW, resolve
parent fd + basename for leaf symlinkat/move_mount/open_tree/openat,
and use AT_SYMLINK_NOFOLLOW in existsAs*At helpers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant