루트가 아닌 사용자 네임스페이스, nosuid, nodev 파일 시스템에서 바인드 마운트는 성공하지만 다시 마운트는 실패하는 이유는 무엇입니까?

루트가 아닌 사용자 네임스페이스, nosuid, nodev 파일 시스템에서 바인드 마운트는 성공하지만 다시 마운트는 실패하는 이유는 무엇입니까?

Linux 사용자 네임스페이스에서는 루트가 아닌 사용자로서 /tmp/foo자신에게 마운트를 바인딩합니다. 그게 다야.

/tmp/foo그런 다음 읽기 전용으로 다시 설치해 보았습니다 . 또는 를 /tmp사용하여 nosuid마운트 한 경우 nodev다시 마운트가 실패합니다. 그렇지 않으면 다시 마운트가 성공합니다.

재설치가 성공하지 못하게 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

답변1

나는 답을 찾았습니다.

아래 발췌된 커널 소스 코드에서 볼 수 있듯이 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;
}

관련 정보