@@ -759,6 +759,34 @@ TEST(OpenTreeTest, OpenTreeCloneNonRecursiveOnAttached) {
759759 SyscallFailsWithErrno (ENOENT ));
760760}
761761
762+ TEST (OpenTreeTest, OpenTreeCloneFailsOnDetachedMount) {
763+ SKIP_IF (!ASSERT_NO_ERRNO_AND_VALUE (HaveCapability (CAP_SYS_ADMIN )));
764+
765+ // Create a detached tmpfs mount fd via fsopen/fsmount (no path attachment).
766+ int fsfd = fsopen (kTmpfs , 0 );
767+ ASSERT_THAT (fsfd, SyscallSucceeds ());
768+ auto cleanup_fs = Cleanup ([&]() { close (fsfd); });
769+ ASSERT_THAT (fsconfig (fsfd, FSCONFIG_CMD_CREATE , NULL , NULL , 0 ),
770+ SyscallSucceeds ());
771+ int mntfd = fsmount (fsfd, 0 , 0 );
772+ ASSERT_THAT (mntfd, SyscallSucceeds ());
773+ auto cleanup_mnt = Cleanup ([&]() { close (mntfd); });
774+
775+ // Attach the mount to a path and then detach it via MNT_DETACH while
776+ // keeping the mount fd alive.
777+ const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE (TempPath::CreateDir ());
778+ ASSERT_THAT (move_mount (mntfd, " " , AT_FDCWD , dir.path ().c_str (),
779+ MOVE_MOUNT_F_EMPTY_PATH ),
780+ SyscallSucceeds ());
781+ ASSERT_THAT (umount2 (dir.path ().c_str (), MNT_DETACH ), SyscallSucceeds ());
782+
783+ // open_tree(OPEN_TREE_CLONE) on a mount that has already been detached via
784+ // MNT_DETACH must fail with EINVAL, matching Linux. Regression test for
785+ // CloneTreeToAnonNS() not checking Mount.umounted (gvisor.dev/issue/13481).
786+ EXPECT_THAT (open_tree (mntfd, " " , AT_EMPTY_PATH | OPEN_TREE_CLONE ),
787+ SyscallFailsWithErrno (EINVAL ));
788+ }
789+
762790TEST (OpenTreeTest, OpenTreeCloneNonRecursiveOnDetached) {
763791 SKIP_IF (!ASSERT_NO_ERRNO_AND_VALUE (HaveCapability (CAP_SYS_ADMIN )));
764792
0 commit comments