Skip to content

mnt: apply per-mount nosuid/nodev/noexec recursively to recursive binds - #284

Open
h1-mrz wants to merge 1 commit into
google:masterfrom
h1-mrz:harden-recursive-bind-flags
Open

mnt: apply per-mount nosuid/nodev/noexec recursively to recursive binds#284
h1-mrz wants to merge 1 commit into
google:masterfrom
h1-mrz:harden-recursive-bind-flags

Conversation

@h1-mrz

@h1-mrz h1-mrz commented Jul 18, 2026

Copy link
Copy Markdown

Every bind mount is forced recursive (MS_REC is OR'd on in prepareMountPoint), so the source subtree -- including any nested submounts -- is cloned into the jail. The requested per-mount flags (nosuid/nodev/noexec) were only applied to the top mount:

  • newapi: applyMountFlags() called mount_setattr() with AT_EMPTY_PATH only, even though doBindMountAt() clones the tree with open_tree(AT_RECURSIVE);
  • legacy: remountPt() issued a single non-recursive MS_REMOUNT|MS_BIND on dst.

As a result, a bind whose source subtree contains a nested mount (a dev-bearing fs, or one holding a setuid binary) kept that submount's original dev/suid/exec-permitting attributes inside the jail, so an explicitly requested nodev/nosuid was not enforced on it. The recursive read-only pass already applies MOUNT_ATTR_RDONLY with AT_RECURSIVE, so the restriction flags were the only per-mount attributes left non-recursive.

Apply the flags recursively when the bind is recursive: thread a recursive bool into the newapi applyMountFlags() (OR AT_RECURSIVE into mount_setattr), and re-apply the flags to every submount under dst in the legacy remountPt() by walking /proc/self/mountinfo.

Verified (legacy backend): with a nodev,nosuid recursive bind of a tree containing a nested tmpfs, the nested submount inside the jail now shows nosuid,nodev (previously it kept the host's dev/suid-honoring attributes).

Every bind mount is forced recursive (MS_REC is OR'd on in prepareMountPoint),
so the source subtree -- including any nested submounts -- is cloned into the
jail. The requested per-mount flags (nosuid/nodev/noexec) were only applied to
the top mount:
 - newapi: applyMountFlags() called mount_setattr() with AT_EMPTY_PATH only,
   even though doBindMountAt() clones the tree with open_tree(AT_RECURSIVE);
 - legacy: remountPt() issued a single non-recursive MS_REMOUNT|MS_BIND on dst.

As a result, a bind whose source subtree contains a nested mount (a dev-bearing
fs, or one holding a setuid binary) kept that submount's original
dev/suid/exec-permitting attributes inside the jail, so an explicitly requested
nodev/nosuid was not enforced on it. The recursive read-only pass already
applies MOUNT_ATTR_RDONLY with AT_RECURSIVE, so the restriction flags were the
only per-mount attributes left non-recursive.

Apply the flags recursively when the bind is recursive: thread a `recursive`
bool into the newapi applyMountFlags() (OR AT_RECURSIVE into mount_setattr), and
re-apply the flags to every submount under dst in the legacy remountPt() by
walking /proc/self/mountinfo.

Verified (legacy backend): with a nodev,nosuid recursive bind of a tree
containing a nested tmpfs, the nested submount inside the jail now shows
nosuid,nodev (previously it kept the host's dev/suid-honoring attributes).
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