ITADN

open_tree(OPEN_TREE_CLONE) succeeds on MNT_DETACH unmounted mount

#13481Openprasanna8585 创建于 2026-06-17
type: bug
P
prasanna8585commented
### Description **Expected behavior:** `open_tree(2)` with `OPEN_TREE_CLONE` on a mount that has already been detached via `umount2(MNT_DETACH)` should fail with `EINVAL`, matching Linux. **Observed behavior:** gVisor allows the clone to succeed. The detached mount can then be re-attached anywhere via `move_mount(2)`, fully reversing the `umount2(MNT_DETACH)` and restoring access to a filesystem that was intentionally detached. **Root cause:** `CloneTreeToAnonNS()` in `pkg/sentry/vfs/namespace.go` checks namespace membership (`fromMnt.ns != taskMountNs`) but never checks `fromMnt.umounted`. After `MNT_DETACH`, `mount.umounted` is set to `true` but `mount.ns` is **not** cleared until the last reference is dropped. When the detached mount's `.ns` still equals the current task's mount namespace (the common case for a detached-but-referenced mount), the validation condition evaluates to `false` and the function falls through to `cloneMount` with no `umounted` guard at all: ``` fsName := fromMnt.Filesystem().FilesystemType().Name() if fromMnt.ns != taskMountNs && (fromMnt.ns == nil || !fromMnt.ns.anonCanBeOperatedOn(taskMountNs)) && fsName != nsfsName { return nil, linuxerr.EINVAL } // fromMnt.umounted is never checked here. ``` This is the same class of bug fixed in commit `6a112c60a257dadac59962e0bc9e9b5aee70b5b6` (2024) for `BindAt`, `ConnectMountAt`, `MoveMountAt`, and `propagateMount` — all of which now check `.umounted` via the `validInMountNS()` helper introduced by that fix. `CloneTreeToAnonNS` was added separately as part of the `open_tree(2)` / new VFS mount API and was not covered. **Proposed fix:** ``` - if fromMnt.ns != taskMountNs && (fromMnt.ns == nil || !fromMnt.ns.anonCanBeOperatedOn(taskMountNs)) && fsName != nsfsName { + if fromMnt.umounted || (fromMnt.ns != taskMountNs && (fromMnt.ns == nil || !fromMnt.ns.anonCanBeOperatedOn(taskMountNs)) && fsName != nsfsName) { return nil, linuxerr.EINVAL } ``` Will follow up with a PR containing this fix plus a regression test (`DetachedMountOpenTreeCloneFails`), analogous to `DetachedMountBindFails` added by the 2024 fix. **Prior disclosure:** Reported to gvisor-security@googlegroups.com on 2026-06-14, confirmed as a real, gVisor-specific behavioral deviation, classified Integrity/SandboxUser (confined to the sandbox, not a sandbox escape — does not qualify for VRP reward, but is a real bug worth fixing per the maintainer's own reply). ### Steps to reproduce Requires `CAP_SYS_ADMIN` (standard for container root). 1. Mount tmpfs: ``` mount("tmpfs", "/tmp/A", "tmpfs", 0, "size=1m"); ``` 2. Hold an `O_PATH` reference (keeps the mount alive after detach): ``` int fd = open("/tmp/A", O_PATH | O_DIRECTORY); ``` 3. Detach the mount: ``` umount2("/tmp/A", MNT_DETACH); ``` `/tmp/A` is no longer accessible by path. `mount.umounted` is now `true`, but `mount.ns` is unchanged. 4. Clone the detached mount via the held fd: ``` long tree_fd = syscall(__NR_open_tree, fd, "", OPEN_TREE_CLONE | AT_EMPTY_PATH); ``` - **Linux:** fails with `errno = EINVAL` - **gVisor:** succeeds, returns a valid `tree_fd` 5. Re-attach the cloned (detached) filesystem at a new path: ``` syscall(__NR_move_mount, (int)tree_fd, "", AT_FDCWD, "/tmp/B", MOVE_MOUNT_F_EMPTY_PATH); ``` On gVisor this succeeds, and files from the originally-unmounted tmpfs (e.g. a sentinel file written before step 3) are readable again at `/tmp/B`. A complete, self-contained PoC (`poc_gvisor_detached_clone.c`) that performs all five steps and prints pass/fail at each stage is attached to the original email report and will be linked in the follow-up PR. ### runsc version ```shell runsc version: master / HEAD as of 2026-06-14 Reproduced against: pkg/sentry/vfs/namespace.go @ HEAD sha256: cad58c41d16f7bb0e99eb64da64ffe1489e86682cd053b4d8f3dfd7b769ae640 Confirmed Linux baseline behavior (EINVAL) on standard Linux 6.x ``` ### docker version (if using docker) ```shell ``` ### uname _No response_ ### kubectl (if using Kubernetes) ```shell ``` ### repo state (if built from source) _No response_ ### runsc debug logs (if available) ```shell ```
0 条评论