Merge tag 'ovl-update-5.19' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 30 May 2022 18:19:16 +0000 (11:19 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 30 May 2022 18:19:16 +0000 (11:19 -0700)
Pull overlayfs updates from Miklos Szeredi:

 - Support idmapped layers in overlayfs (Christian Brauner)

 - Add a fix to exportfs that is relevant to open_by_handle_at(2) as
   well

 - Introduce new lookup helpers that allow passing mnt_userns into
   inode_permission()

* tag 'ovl-update-5.19' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs:
  ovl: support idmapped layers
  ovl: handle idmappings in ovl_xattr_{g,s}et()
  ovl: handle idmappings in layer open helpers
  ovl: handle idmappings in ovl_permission()
  ovl: use ovl_copy_{real,upper}attr() wrappers
  ovl: store lower path in ovl_inode
  ovl: handle idmappings for layer lookup
  ovl: handle idmappings for layer fileattrs
  ovl: use ovl_path_getxattr() wrapper
  ovl: use ovl_lookup_upper() wrapper
  ovl: use ovl_do_notify_change() wrapper
  ovl: pass layer mnt to ovl_open_realfile()
  ovl: pass ofs to setattr operations
  ovl: handle idmappings in creation operations
  ovl: add ovl_upper_mnt_userns() wrapper
  ovl: pass ofs to creation operations
  ovl: use wrappers to all vfs_*xattr() calls
  exportfs: support idmapped mounts
  fs: add two trivial lookup helpers

1  2 
fs/namei.c
fs/overlayfs/file.c

diff --combined fs/namei.c
index 3dc0db2df561835e78e6e3ab6b301d26d3f637fc,5f91d526a86e850e9dfe2de98781a80b4a1b68e5..776ecf6799650c28702780749cf5102ebe7a0f4c
@@@ -22,7 -22,6 +22,7 @@@
  #include <linux/fs.h>
  #include <linux/namei.h>
  #include <linux/pagemap.h>
 +#include <linux/sched/mm.h>
  #include <linux/fsnotify.h>
  #include <linux/personality.h>
  #include <linux/security.h>
@@@ -1032,7 -1031,7 +1032,7 @@@ static struct ctl_table namei_sysctls[
                .procname       = "protected_symlinks",
                .data           = &sysctl_protected_symlinks,
                .maxlen         = sizeof(int),
 -              .mode           = 0600,
 +              .mode           = 0644,
                .proc_handler   = proc_dointvec_minmax,
                .extra1         = SYSCTL_ZERO,
                .extra2         = SYSCTL_ONE,
                .procname       = "protected_hardlinks",
                .data           = &sysctl_protected_hardlinks,
                .maxlen         = sizeof(int),
 -              .mode           = 0600,
 +              .mode           = 0644,
                .proc_handler   = proc_dointvec_minmax,
                .extra1         = SYSCTL_ZERO,
                .extra2         = SYSCTL_ONE,
                .procname       = "protected_fifos",
                .data           = &sysctl_protected_fifos,
                .maxlen         = sizeof(int),
 -              .mode           = 0600,
 +              .mode           = 0644,
                .proc_handler   = proc_dointvec_minmax,
                .extra1         = SYSCTL_ZERO,
                .extra2         = SYSCTL_TWO,
                .procname       = "protected_regular",
                .data           = &sysctl_protected_regular,
                .maxlen         = sizeof(int),
 -              .mode           = 0600,
 +              .mode           = 0644,
                .proc_handler   = proc_dointvec_minmax,
                .extra1         = SYSCTL_ZERO,
                .extra2         = SYSCTL_TWO,
@@@ -2769,7 -2768,8 +2769,8 @@@ struct dentry *lookup_one(struct user_n
  EXPORT_SYMBOL(lookup_one);
  
  /**
-  * lookup_one_len_unlocked - filesystem helper to lookup single pathname component
+  * lookup_one_unlocked - filesystem helper to lookup single pathname component
+  * @mnt_userns:       idmapping of the mount the lookup is performed from
   * @name:     pathname component to lookup
   * @base:     base directory to lookup from
   * @len:      maximum length @len should be interpreted to
   * Unlike lookup_one_len, it should be called without the parent
   * i_mutex held, and will take the i_mutex itself if necessary.
   */
- struct dentry *lookup_one_len_unlocked(const char *name,
-                                      struct dentry *base, int len)
+ struct dentry *lookup_one_unlocked(struct user_namespace *mnt_userns,
+                                  const char *name, struct dentry *base,
+                                  int len)
  {
        struct qstr this;
        int err;
        struct dentry *ret;
  
-       err = lookup_one_common(&init_user_ns, name, base, len, &this);
+       err = lookup_one_common(mnt_userns, name, base, len, &this);
        if (err)
                return ERR_PTR(err);
  
                ret = lookup_slow(&this, base, 0);
        return ret;
  }
+ EXPORT_SYMBOL(lookup_one_unlocked);
+ /**
+  * lookup_one_positive_unlocked - filesystem helper to lookup single
+  *                              pathname component
+  * @mnt_userns:       idmapping of the mount the lookup is performed from
+  * @name:     pathname component to lookup
+  * @base:     base directory to lookup from
+  * @len:      maximum length @len should be interpreted to
+  *
+  * This helper will yield ERR_PTR(-ENOENT) on negatives. The helper returns
+  * known positive or ERR_PTR(). This is what most of the users want.
+  *
+  * Note that pinned negative with unlocked parent _can_ become positive at any
+  * time, so callers of lookup_one_unlocked() need to be very careful; pinned
+  * positives have >d_inode stable, so this one avoids such problems.
+  *
+  * Note that this routine is purely a helper for filesystem usage and should
+  * not be called by generic code.
+  *
+  * The helper should be called without i_mutex held.
+  */
+ struct dentry *lookup_one_positive_unlocked(struct user_namespace *mnt_userns,
+                                           const char *name,
+                                           struct dentry *base, int len)
+ {
+       struct dentry *ret = lookup_one_unlocked(mnt_userns, name, base, len);
+       if (!IS_ERR(ret) && d_flags_negative(smp_load_acquire(&ret->d_flags))) {
+               dput(ret);
+               ret = ERR_PTR(-ENOENT);
+       }
+       return ret;
+ }
+ EXPORT_SYMBOL(lookup_one_positive_unlocked);
+ /**
+  * lookup_one_len_unlocked - filesystem helper to lookup single pathname component
+  * @name:     pathname component to lookup
+  * @base:     base directory to lookup from
+  * @len:      maximum length @len should be interpreted to
+  *
+  * Note that this routine is purely a helper for filesystem usage and should
+  * not be called by generic code.
+  *
+  * Unlike lookup_one_len, it should be called without the parent
+  * i_mutex held, and will take the i_mutex itself if necessary.
+  */
+ struct dentry *lookup_one_len_unlocked(const char *name,
+                                      struct dentry *base, int len)
+ {
+       return lookup_one_unlocked(&init_user_ns, name, base, len);
+ }
  EXPORT_SYMBOL(lookup_one_len_unlocked);
  
  /*
  struct dentry *lookup_positive_unlocked(const char *name,
                                       struct dentry *base, int len)
  {
-       struct dentry *ret = lookup_one_len_unlocked(name, base, len);
-       if (!IS_ERR(ret) && d_flags_negative(smp_load_acquire(&ret->d_flags))) {
-               dput(ret);
-               ret = ERR_PTR(-ENOENT);
-       }
-       return ret;
+       return lookup_one_positive_unlocked(&init_user_ns, name, base, len);
  }
  EXPORT_SYMBOL(lookup_positive_unlocked);
  
@@@ -3674,14 -3723,18 +3724,14 @@@ static struct dentry *filename_create(i
  {
        struct dentry *dentry = ERR_PTR(-EEXIST);
        struct qstr last;
 +      bool want_dir = lookup_flags & LOOKUP_DIRECTORY;
 +      unsigned int reval_flag = lookup_flags & LOOKUP_REVAL;
 +      unsigned int create_flags = LOOKUP_CREATE | LOOKUP_EXCL;
        int type;
        int err2;
        int error;
 -      bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
  
 -      /*
 -       * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any
 -       * other flags passed in are ignored!
 -       */
 -      lookup_flags &= LOOKUP_REVAL;
 -
 -      error = filename_parentat(dfd, name, lookup_flags, path, &last, &type);
 +      error = filename_parentat(dfd, name, reval_flag, path, &last, &type);
        if (error)
                return ERR_PTR(error);
  
        /* don't fail immediately if it's r/o, at least try to report other errors */
        err2 = mnt_want_write(path->mnt);
        /*
 -       * Do the final lookup.
 +       * Do the final lookup.  Suppress 'create' if there is a trailing
 +       * '/', and a directory wasn't requested.
         */
 -      lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
 +      if (last.name[last.len] && !want_dir)
 +              create_flags = 0;
        inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT);
 -      dentry = __lookup_hash(&last, path->dentry, lookup_flags);
 +      dentry = __lookup_hash(&last, path->dentry, reval_flag | create_flags);
        if (IS_ERR(dentry))
                goto unlock;
  
         * all is fine. Let's be bastards - you had / on the end, you've
         * been asking for (non-existent) directory. -ENOENT for you.
         */
 -      if (unlikely(!is_dir && last.name[last.len])) {
 +      if (unlikely(!create_flags)) {
                error = -ENOENT;
                goto fail;
        }
@@@ -5002,28 -5053,28 +5052,28 @@@ int page_readlink(struct dentry *dentry
  }
  EXPORT_SYMBOL(page_readlink);
  
 -/*
 - * The nofs argument instructs pagecache_write_begin to pass AOP_FLAG_NOFS
 - */
 -int __page_symlink(struct inode *inode, const char *symname, int len, int nofs)
 +int page_symlink(struct inode *inode, const char *symname, int len)
  {
        struct address_space *mapping = inode->i_mapping;
 +      const struct address_space_operations *aops = mapping->a_ops;
 +      bool nofs = !mapping_gfp_constraint(mapping, __GFP_FS);
        struct page *page;
        void *fsdata;
        int err;
 -      unsigned int flags = 0;
 -      if (nofs)
 -              flags |= AOP_FLAG_NOFS;
 +      unsigned int flags;
  
  retry:
 -      err = pagecache_write_begin(NULL, mapping, 0, len-1,
 -                              flags, &page, &fsdata);
 +      if (nofs)
 +              flags = memalloc_nofs_save();
 +      err = aops->write_begin(NULL, mapping, 0, len-1, &page, &fsdata);
 +      if (nofs)
 +              memalloc_nofs_restore(flags);
        if (err)
                goto fail;
  
        memcpy(page_address(page), symname, len-1);
  
 -      err = pagecache_write_end(NULL, mapping, 0, len-1, len-1,
 +      err = aops->write_end(NULL, mapping, 0, len-1, len-1,
                                                        page, fsdata);
        if (err < 0)
                goto fail;
  fail:
        return err;
  }
 -EXPORT_SYMBOL(__page_symlink);
 -
 -int page_symlink(struct inode *inode, const char *symname, int len)
 -{
 -      return __page_symlink(inode, symname, len,
 -                      !mapping_gfp_constraint(inode->i_mapping, __GFP_FS));
 -}
  EXPORT_SYMBOL(page_symlink);
  
  const struct inode_operations page_symlink_inode_operations = {
diff --combined fs/overlayfs/file.c
index 9d69b4dbb8c4cbc269598676540e1cf3c34f0e15,3fac8a0c674c8889cfcabdd0302ce8f0f0245d85..daff601b5c41011c1285f760e0c9b589503d0075
@@@ -38,9 -38,11 +38,11 @@@ static char ovl_whatisit(struct inode *
  #define OVL_OPEN_FLAGS (O_NOATIME | FMODE_NONOTIFY)
  
  static struct file *ovl_open_realfile(const struct file *file,
-                                     struct inode *realinode)
+                                     struct path *realpath)
  {
+       struct inode *realinode = d_inode(realpath->dentry);
        struct inode *inode = file_inode(file);
+       struct user_namespace *real_mnt_userns;
        struct file *realfile;
        const struct cred *old_cred;
        int flags = file->f_flags | OVL_OPEN_FLAGS;
                acc_mode |= MAY_APPEND;
  
        old_cred = ovl_override_creds(inode->i_sb);
-       err = inode_permission(&init_user_ns, realinode, MAY_OPEN | acc_mode);
+       real_mnt_userns = mnt_user_ns(realpath->mnt);
+       err = inode_permission(real_mnt_userns, realinode, MAY_OPEN | acc_mode);
        if (err) {
                realfile = ERR_PTR(err);
        } else {
-               if (!inode_owner_or_capable(&init_user_ns, realinode))
+               if (!inode_owner_or_capable(real_mnt_userns, realinode))
                        flags &= ~O_NOATIME;
  
                realfile = open_with_fake_path(&file->f_path, flags, realinode,
@@@ -82,8 -85,11 +85,8 @@@ static int ovl_change_flags(struct fil
        if (((flags ^ file->f_flags) & O_APPEND) && IS_APPEND(inode))
                return -EPERM;
  
 -      if (flags & O_DIRECT) {
 -              if (!file->f_mapping->a_ops ||
 -                  !file->f_mapping->a_ops->direct_IO)
 -                      return -EINVAL;
 -      }
 +      if ((flags & O_DIRECT) && !(file->f_mode & FMODE_CAN_ODIRECT))
 +              return -EINVAL;
  
        if (file->f_op->check_flags) {
                err = file->f_op->check_flags(flags);
  static int ovl_real_fdget_meta(const struct file *file, struct fd *real,
                               bool allow_meta)
  {
-       struct inode *inode = file_inode(file);
-       struct inode *realinode;
+       struct dentry *dentry = file_dentry(file);
+       struct path realpath;
  
        real->flags = 0;
        real->file = file->private_data;
  
        if (allow_meta)
-               realinode = ovl_inode_real(inode);
+               ovl_path_real(dentry, &realpath);
        else
-               realinode = ovl_inode_realdata(inode);
+               ovl_path_realdata(dentry, &realpath);
  
        /* Has it been copied up since we'd opened it? */
-       if (unlikely(file_inode(real->file) != realinode)) {
+       if (unlikely(file_inode(real->file) != d_inode(realpath.dentry))) {
                real->flags = FDPUT_FPUT;
-               real->file = ovl_open_realfile(file, realinode);
+               real->file = ovl_open_realfile(file, &realpath);
  
                return PTR_ERR_OR_ZERO(real->file);
        }
@@@ -141,17 -147,20 +144,20 @@@ static int ovl_real_fdget(const struct 
  
  static int ovl_open(struct inode *inode, struct file *file)
  {
+       struct dentry *dentry = file_dentry(file);
        struct file *realfile;
+       struct path realpath;
        int err;
  
-       err = ovl_maybe_copy_up(file_dentry(file), file->f_flags);
+       err = ovl_maybe_copy_up(dentry, file->f_flags);
        if (err)
                return err;
  
        /* No longer need these flags, so don't pass them on to underlying fs */
        file->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
  
-       realfile = ovl_open_realfile(file, ovl_inode_realdata(inode));
+       ovl_path_realdata(dentry, &realpath);
+       realfile = ovl_open_realfile(file, &realpath);
        if (IS_ERR(realfile))
                return PTR_ERR(realfile);
  
@@@ -270,7 -279,7 +276,7 @@@ static void ovl_aio_cleanup_handler(str
                __sb_writers_acquired(file_inode(iocb->ki_filp)->i_sb,
                                      SB_FREEZE_WRITE);
                file_end_write(iocb->ki_filp);
-               ovl_copyattr(ovl_inode_real(inode), inode);
+               ovl_copyattr(inode);
        }
  
        orig_iocb->ki_pos = iocb->ki_pos;
@@@ -303,7 -312,8 +309,7 @@@ static ssize_t ovl_read_iter(struct kio
  
        ret = -EINVAL;
        if (iocb->ki_flags & IOCB_DIRECT &&
 -          (!real.file->f_mapping->a_ops ||
 -           !real.file->f_mapping->a_ops->direct_IO))
 +          !(real.file->f_mode & FMODE_CAN_ODIRECT))
                goto out_fdput;
  
        old_cred = ovl_override_creds(file_inode(file)->i_sb);
@@@ -352,7 -362,7 +358,7 @@@ static ssize_t ovl_write_iter(struct ki
  
        inode_lock(inode);
        /* Update mode */
-       ovl_copyattr(ovl_inode_real(inode), inode);
+       ovl_copyattr(inode);
        ret = file_remove_privs(file);
        if (ret)
                goto out_unlock;
  
        ret = -EINVAL;
        if (iocb->ki_flags & IOCB_DIRECT &&
 -          (!real.file->f_mapping->a_ops ||
 -           !real.file->f_mapping->a_ops->direct_IO))
 +          !(real.file->f_mode & FMODE_CAN_ODIRECT))
                goto out_fdput;
  
        if (!ovl_should_sync(OVL_FS(inode->i_sb)))
                                     ovl_iocb_to_rwf(ifl));
                file_end_write(real.file);
                /* Update size */
-               ovl_copyattr(ovl_inode_real(inode), inode);
+               ovl_copyattr(inode);
        } else {
                struct ovl_aio_req *aio_req;
  
@@@ -426,12 -437,11 +432,11 @@@ static ssize_t ovl_splice_write(struct 
        struct fd real;
        const struct cred *old_cred;
        struct inode *inode = file_inode(out);
-       struct inode *realinode = ovl_inode_real(inode);
        ssize_t ret;
  
        inode_lock(inode);
        /* Update mode */
-       ovl_copyattr(realinode, inode);
+       ovl_copyattr(inode);
        ret = file_remove_privs(out);
        if (ret)
                goto out_unlock;
  
        file_end_write(real.file);
        /* Update size */
-       ovl_copyattr(realinode, inode);
+       ovl_copyattr(inode);
        revert_creds(old_cred);
        fdput(real);
  
@@@ -521,7 -531,7 +526,7 @@@ static long ovl_fallocate(struct file *
        revert_creds(old_cred);
  
        /* Update size */
-       ovl_copyattr(ovl_inode_real(inode), inode);
+       ovl_copyattr(inode);
  
        fdput(real);
  
@@@ -593,7 -603,7 +598,7 @@@ static loff_t ovl_copyfile(struct file 
        revert_creds(old_cred);
  
        /* Update size */
-       ovl_copyattr(ovl_inode_real(inode_out), inode_out);
+       ovl_copyattr(inode_out);
  
        fdput(real_in);
        fdput(real_out);