#include <linux/proc_ns.h>
 #include <linux/magic.h>
 #include <linux/memblock.h>
+#include <linux/proc_fs.h>
 #include <linux/task_work.h>
 #include <linux/sched/task.h>
 #include <uapi/linux/mount.h>
        unsigned int propagation;
        unsigned int lookup_flags;
        bool recurse;
+       struct user_namespace *mnt_userns;
 };
 
 /* /sys/fs */
        (MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | MOUNT_ATTR_NODEV | \
         MOUNT_ATTR_NOEXEC | MOUNT_ATTR__ATIME | MOUNT_ATTR_NODIRATIME)
 
-#define MOUNT_SETATTR_VALID_FLAGS FSMOUNT_VALID_FLAGS
+#define MOUNT_SETATTR_VALID_FLAGS (FSMOUNT_VALID_FLAGS | MOUNT_ATTR_IDMAP)
 
 #define MOUNT_SETATTR_PROPAGATION_FLAGS \
        (MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE | MS_SHARED)
        return flags;
 }
 
+static int can_idmap_mount(const struct mount_kattr *kattr, struct mount *mnt)
+{
+       struct vfsmount *m = &mnt->mnt;
+
+       if (!kattr->mnt_userns)
+               return 0;
+
+       /*
+        * Once a mount has been idmapped we don't allow it to change its
+        * mapping. It makes things simpler and callers can just create
+        * another bind-mount they can idmap if they want to.
+        */
+       if (mnt_user_ns(m) != &init_user_ns)
+               return -EPERM;
+
+       /* The underlying filesystem doesn't support idmapped mounts yet. */
+       if (!(m->mnt_sb->s_type->fs_flags & FS_ALLOW_IDMAP))
+               return -EINVAL;
+
+       /* We're not controlling the superblock. */
+       if (!ns_capable(m->mnt_sb->s_user_ns, CAP_SYS_ADMIN))
+               return -EPERM;
+
+       /* Mount has already been visible in the filesystem hierarchy. */
+       if (!is_anon_ns(mnt->mnt_ns))
+               return -EINVAL;
+
+       return 0;
+}
+
 static struct mount *mount_setattr_prepare(struct mount_kattr *kattr,
                                           struct mount *mnt, int *err)
 {
                        goto out;
                }
 
+               *err = can_idmap_mount(kattr, m);
+               if (*err)
+                       goto out;
+
                last = m;
 
                if ((kattr->attr_set & MNT_READONLY) &&
        return last;
 }
 
+static void do_idmap_mount(const struct mount_kattr *kattr, struct mount *mnt)
+{
+       struct user_namespace *mnt_userns;
+
+       if (!kattr->mnt_userns)
+               return;
+
+       mnt_userns = get_user_ns(kattr->mnt_userns);
+       /* Pairs with smp_load_acquire() in mnt_user_ns(). */
+       smp_store_release(&mnt->mnt.mnt_userns, mnt_userns);
+}
+
 static void mount_setattr_commit(struct mount_kattr *kattr,
                                 struct mount *mnt, struct mount *last,
                                 int err)
                if (!err) {
                        unsigned int flags;
 
+                       do_idmap_mount(kattr, m);
                        flags = recalc_flags(kattr, m);
                        WRITE_ONCE(m->mnt.mnt_flags, flags);
                }
        return err;
 }
 
-static int build_mount_kattr(const struct mount_attr *attr,
+static int build_mount_idmapped(const struct mount_attr *attr, size_t usize,
+                               struct mount_kattr *kattr, unsigned int flags)
+{
+       int err = 0;
+       struct ns_common *ns;
+       struct user_namespace *mnt_userns;
+       struct file *file;
+
+       if (!((attr->attr_set | attr->attr_clr) & MOUNT_ATTR_IDMAP))
+               return 0;
+
+       /*
+        * We currently do not support clearing an idmapped mount. If this ever
+        * is a use-case we can revisit this but for now let's keep it simple
+        * and not allow it.
+        */
+       if (attr->attr_clr & MOUNT_ATTR_IDMAP)
+               return -EINVAL;
+
+       if (attr->userns_fd > INT_MAX)
+               return -EINVAL;
+
+       file = fget(attr->userns_fd);
+       if (!file)
+               return -EBADF;
+
+       if (!proc_ns_file(file)) {
+               err = -EINVAL;
+               goto out_fput;
+       }
+
+       ns = get_proc_ns(file_inode(file));
+       if (ns->ops->type != CLONE_NEWUSER) {
+               err = -EINVAL;
+               goto out_fput;
+       }
+
+       /*
+        * The init_user_ns is used to indicate that a vfsmount is not idmapped.
+        * This is simpler than just having to treat NULL as unmapped. Users
+        * wanting to idmap a mount to init_user_ns can just use a namespace
+        * with an identity mapping.
+        */
+       mnt_userns = container_of(ns, struct user_namespace, ns);
+       if (mnt_userns == &init_user_ns) {
+               err = -EPERM;
+               goto out_fput;
+       }
+       kattr->mnt_userns = get_user_ns(mnt_userns);
+
+out_fput:
+       fput(file);
+       return err;
+}
+
+static int build_mount_kattr(const struct mount_attr *attr, size_t usize,
                             struct mount_kattr *kattr, unsigned int flags)
 {
        unsigned int lookup_flags = LOOKUP_AUTOMOUNT | LOOKUP_FOLLOW;
        if ((attr->attr_set | attr->attr_clr) & ~MOUNT_SETATTR_VALID_FLAGS)
                return -EINVAL;
 
-       if (attr->userns_fd)
-               return -EINVAL;
-
        kattr->attr_set = attr_flags_to_mnt_flags(attr->attr_set);
        kattr->attr_clr = attr_flags_to_mnt_flags(attr->attr_clr);
 
                        return -EINVAL;
        }
 
-       return 0;
+       return build_mount_idmapped(attr, usize, kattr, flags);
+}
+
+static void finish_mount_kattr(struct mount_kattr *kattr)
+{
+       put_user_ns(kattr->mnt_userns);
+       kattr->mnt_userns = NULL;
 }
 
 SYSCALL_DEFINE5(mount_setattr, int, dfd, const char __user *, path,
            attr.propagation == 0)
                return 0;
 
-       err = build_mount_kattr(&attr, &kattr, flags);
+       err = build_mount_kattr(&attr, usize, &kattr, flags);
        if (err)
                return err;
 
                return err;
 
        err = do_mount_setattr(&target, &kattr);
+       finish_mount_kattr(&kattr);
        path_put(&target);
        return err;
 }