Linux

在非 root 使用者命名空間中,在 nosuid、nodev 文件系統上,為什麼綁定掛載成功但重新掛載失敗?

  • June 23, 2021

在 Linux 使用者命名空間中,作為非 root 使用者,我將 mount 綁定/tmp/foo到自身。這成功了。

然後,我嘗試重新安裝/tmp/foo為只讀。如果/tmp使用nosuidornodev掛載,則重新掛載失敗。否則,重新掛載成功。

是否有某些原因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.cLinux核心原始碼中:

/*
* 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;
}

引用自:https://unix.stackexchange.com/questions/655409