In a Linux user namespace, as non-root, I bind mount /tmp/foo to itself. This succeeds.

Then, I try to remount /tmp/foo to be read-only. If /tmp is mounted with nosuid or nodev, then the remount fails. Otherwise, the remount succeeds.

Is there some reason why nosuid and/or nodev prevent the remount from succeeding? Is this behavior documented somewhere? I'm puzzled, as I would expect the bind mount and remount to either both succeed, or both fail.

Here is the code to reproduce the bind mount and remount:

#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;


Sample output:

$  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

Whereas, if /tmp is mounted with neither nosuid nor nodev, then the bind mount and the remount will both succeed, as follows:

$  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 Answer 1


I have found the answer.

As can be seen in the below excerpts from the kernel source code, if any of the flags NODEV, NOSUID, NOEXEC, and/or ATIME are already set, I will need to preserve (i.e. continue to set) them in my second call to mount().

From fs/namespace.c in the Linux kernel source code:

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

        ret = change_mount_ro_state(mnt, mnt_flags);
        if (ret == 0)
                set_mount_attributes(mnt, mnt_flags);

        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;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.