Linux
在非 root 使用者命名空間中,在 nosuid、nodev 文件系統上,為什麼綁定掛載成功但重新掛載失敗?
在 Linux 使用者命名空間中,作為非 root 使用者,我將 mount 綁定
/tmp/foo
到自身。這成功了。然後,我嘗試重新安裝
/tmp/foo
為只讀。如果/tmp
使用nosuid
ornodev
掛載,則重新掛載失敗。否則,重新掛載成功。是否有某些原因
nosuid
和/或nodev
阻止重新安裝成功?這種行為是否記錄在某處?我很困惑,因為我希望綁定掛載和重新掛載都成功,或者都失敗。這是重現綁定掛載和重新掛載的程式碼:
#define _GNU_SOURCE /* unshare */ #include <errno.h> /* errno */ #include <sched.h> /* unshare */ #include <stdio.h> /* printf */ #include <string.h> /* strerror */ #include <sys/mount.h> /* mount */ #include <unistd.h> /* getuid */ int main() { printf ( "getuid %d\n", getuid() ); int rv = unshare ( CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUSER ); printf ( "unshare %2d %s\n", rv, strerror(errno) ); rv = mount ( "/tmp/foo", "/tmp/foo", 0, MS_BIND | MS_REC, 0 ), printf ( "mount %2d %s\n", rv, strerror(errno) ); rv = mount ( "/tmp/foo", "/tmp/foo", 0, MS_BIND | MS_REMOUNT | MS_RDONLY, 0 ), printf ( "remount %2d %s\n", rv, strerror(errno) ); return 0; }
樣本輸出:
$ mkdir -p /tmp/foo $ mount | grep /tmp tmpfs on /tmp type tmpfs (rw,nosuid,nodev,relatime,inode64) $ gcc test.c && ./a.out getuid 1000 unshare 0 No error information mount 0 No error information remount -1 Operation not permitted $ uname -a Linux hostname 5.12.12_1 #1 SMP 1624132767 x86_64 GNU/Linux
然而,如果
/tmp
掛載時既沒有nosuid
也沒有nodev
,則綁定掛載和重新掛載都會成功,如下所示:$ mkdir -p /tmp/foo $ mount | grep /tmp tmpfs on /tmp type tmpfs (rw,relatime,inode64) $ gcc test.c && ./a.out getuid 1000 unshare 0 No error information mount 0 No error information remount 0 No error information
我找到了答案。
從核心原始碼的以下摘錄中可以看出,如果已經設置了任何標誌 NODEV、NOSUID、NOEXEC 和/或 ATIME,我將需要在第二次呼叫時保留(即繼續設置)它們
mount()
.從
fs/namespace.c
Linux核心原始碼中:/* * Handle reconfiguration of the mountpoint only without alteration of the * superblock it refers to. This is triggered by specifying MS_REMOUNT|MS_BIND * to mount(2). */ static int do_reconfigure_mnt(struct path *path, unsigned int mnt_flags) { struct super_block *sb = path->mnt->mnt_sb; struct mount *mnt = real_mount(path->mnt); int ret; if (!check_mnt(mnt)) return -EINVAL; if (path->dentry != mnt->mnt.mnt_root) return -EINVAL; if (!can_change_locked_flags(mnt, mnt_flags)) return -EPERM; down_write(&sb->s_umount); ret = change_mount_ro_state(mnt, mnt_flags); if (ret == 0) set_mount_attributes(mnt, mnt_flags); up_write(&sb->s_umount); mnt_warn_timestamp_expiry(path, &mnt->mnt); return ret; } static bool can_change_locked_flags(struct mount *mnt, unsigned int mnt_flags) { unsigned int fl = mnt->mnt.mnt_flags; if ((fl & MNT_LOCK_READONLY) && !(mnt_flags & MNT_READONLY)) return false; if ((fl & MNT_LOCK_NODEV) && !(mnt_flags & MNT_NODEV)) return false; if ((fl & MNT_LOCK_NOSUID) && !(mnt_flags & MNT_NOSUID)) return false; if ((fl & MNT_LOCK_NOEXEC) && !(mnt_flags & MNT_NOEXEC)) return false; if ((fl & MNT_LOCK_ATIME) && ((fl & MNT_ATIME_MASK) != (mnt_flags & MNT_ATIME_MASK))) return false; return true; }