]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
Merge branch 'work.sane_pwd' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 12 May 2017 18:39:59 +0000 (11:39 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 12 May 2017 18:39:59 +0000 (11:39 -0700)
Pull misc vfs updates from Al Viro:
 "Making sure that something like a referral point won't end up as pwd
  or root.

  The main part is the last commit (fixing mntns_install()); that one
  fixes a hard-to-hit race. The fchdir() commit is making fchdir(2) a
  bit more robust - it should be impossible to get opened files (even
  O_PATH ones) for referral points in the first place, so the existing
  checks are OK, but checking the same thing as in chdir(2) is just as
  cheap.

  The path_init() commit removes a redundant check that shouldn't have
  been there in the first place"

* 'work.sane_pwd' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  make sure that mntns_install() doesn't end up with referral for root
  path_init(): don't bother with checking MAY_EXEC for LOOKUP_ROOT
  make sure that fchdir() won't accept referral points, etc.

1  2 
fs/namei.c
fs/namespace.c
fs/open.c

diff --combined fs/namei.c
index 7286f87ce863568bbe1b84f8e432b809118146b5,646db9cf257936e562849241057fa628284bd518..6571a5f5112ed82894fdbec1c9b6dfaf557d93b1
@@@ -340,14 -340,22 +340,14 @@@ int generic_permission(struct inode *in
  
        if (S_ISDIR(inode->i_mode)) {
                /* DACs are overridable for directories */
 -              if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
 -                      return 0;
                if (!(mask & MAY_WRITE))
                        if (capable_wrt_inode_uidgid(inode,
                                                     CAP_DAC_READ_SEARCH))
                                return 0;
 -              return -EACCES;
 -      }
 -      /*
 -       * Read/write DACs are always overridable.
 -       * Executable DACs are overridable when there is
 -       * at least one exec bit set.
 -       */
 -      if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO))
                if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
                        return 0;
 +              return -EACCES;
 +      }
  
        /*
         * Searching includes executable on directories, else just read.
        if (mask == MAY_READ)
                if (capable_wrt_inode_uidgid(inode, CAP_DAC_READ_SEARCH))
                        return 0;
 +      /*
 +       * Read/write DACs are always overridable.
 +       * Executable DACs are overridable when there is
 +       * at least one exec bit set.
 +       */
 +      if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO))
 +              if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
 +                      return 0;
  
        return -EACCES;
  }
@@@ -2142,7 -2142,6 +2142,6 @@@ OK
  
  static const char *path_init(struct nameidata *nd, unsigned flags)
  {
-       int retval = 0;
        const char *s = nd->name->name;
  
        if (!*s)
        if (flags & LOOKUP_ROOT) {
                struct dentry *root = nd->root.dentry;
                struct inode *inode = root->d_inode;
-               if (*s) {
-                       if (!d_can_lookup(root))
-                               return ERR_PTR(-ENOTDIR);
-                       retval = inode_permission(inode, MAY_EXEC);
-                       if (retval)
-                               return ERR_PTR(retval);
-               }
+               if (*s && unlikely(!d_can_lookup(root)))
+                       return ERR_PTR(-ENOTDIR);
                nd->path = nd->root;
                nd->inode = inode;
                if (flags & LOOKUP_RCU) {
@@@ -2258,6 -2252,35 +2252,35 @@@ static inline int lookup_last(struct na
        return walk_component(nd, 0);
  }
  
+ static int handle_lookup_down(struct nameidata *nd)
+ {
+       struct path path = nd->path;
+       struct inode *inode = nd->inode;
+       unsigned seq = nd->seq;
+       int err;
+       if (nd->flags & LOOKUP_RCU) {
+               /*
+                * don't bother with unlazy_walk on failure - we are
+                * at the very beginning of walk, so we lose nothing
+                * if we simply redo everything in non-RCU mode
+                */
+               if (unlikely(!__follow_mount_rcu(nd, &path, &inode, &seq)))
+                       return -ECHILD;
+       } else {
+               dget(path.dentry);
+               err = follow_managed(&path, nd);
+               if (unlikely(err < 0))
+                       return err;
+               inode = d_backing_inode(path.dentry);
+               seq = 0;
+       }
+       path_to_nameidata(&path, nd);
+       nd->inode = inode;
+       nd->seq = seq;
+       return 0;
+ }
  /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
  static int path_lookupat(struct nameidata *nd, unsigned flags, struct path *path)
  {
  
        if (IS_ERR(s))
                return PTR_ERR(s);
+       if (unlikely(flags & LOOKUP_DOWN)) {
+               err = handle_lookup_down(nd);
+               if (unlikely(err < 0)) {
+                       terminate_walk(nd);
+                       return err;
+               }
+       }
        while (!(err = link_path_walk(s, nd))
                && ((err = lookup_last(nd)) > 0)) {
                s = trailing_symlink(nd);
@@@ -4766,7 -4798,7 +4798,7 @@@ int __page_symlink(struct inode *inode
        struct page *page;
        void *fsdata;
        int err;
 -      unsigned int flags = AOP_FLAG_UNINTERRUPTIBLE;
 +      unsigned int flags = 0;
        if (nofs)
                flags |= AOP_FLAG_NOFS;
  
diff --combined fs/namespace.c
index b3b115bd4e1ee397f01bf0930f5f9900be5878bf,0886ef28bff6e0241ad8809bcaae3beffa268590..8bd3e4d448b9f07a8aa148c8d08c092f45471a7c
@@@ -236,6 -236,9 +236,6 @@@ static struct mount *alloc_vfsmnt(cons
                INIT_LIST_HEAD(&mnt->mnt_slave_list);
                INIT_LIST_HEAD(&mnt->mnt_slave);
                INIT_HLIST_NODE(&mnt->mnt_mp_list);
 -#ifdef CONFIG_FSNOTIFY
 -              INIT_HLIST_HEAD(&mnt->mnt_fsnotify_marks);
 -#endif
                init_fs_pin(&mnt->mnt_umount, drop_mountpoint);
        }
        return mnt;
@@@ -3462,8 -3465,9 +3462,9 @@@ static void mntns_put(struct ns_common 
  static int mntns_install(struct nsproxy *nsproxy, struct ns_common *ns)
  {
        struct fs_struct *fs = current->fs;
-       struct mnt_namespace *mnt_ns = to_mnt_ns(ns);
+       struct mnt_namespace *mnt_ns = to_mnt_ns(ns), *old_mnt_ns;
        struct path root;
+       int err;
  
        if (!ns_capable(mnt_ns->user_ns, CAP_SYS_ADMIN) ||
            !ns_capable(current_user_ns(), CAP_SYS_CHROOT) ||
                return -EINVAL;
  
        get_mnt_ns(mnt_ns);
-       put_mnt_ns(nsproxy->mnt_ns);
+       old_mnt_ns = nsproxy->mnt_ns;
        nsproxy->mnt_ns = mnt_ns;
  
        /* Find the root */
-       root.mnt    = &mnt_ns->root->mnt;
-       root.dentry = mnt_ns->root->mnt.mnt_root;
-       path_get(&root);
-       while(d_mountpoint(root.dentry) && follow_down_one(&root))
-               ;
+       err = vfs_path_lookup(mnt_ns->root->mnt.mnt_root, &mnt_ns->root->mnt,
+                               "/", LOOKUP_DOWN, &root);
+       if (err) {
+               /* revert to old namespace */
+               nsproxy->mnt_ns = old_mnt_ns;
+               put_mnt_ns(mnt_ns);
+               return err;
+       }
  
        /* Update the pwd and root */
        set_fs_pwd(fs, &root);
diff --combined fs/open.c
index 6d2d2b33ac54e80374a273e331e2954711186e5a,9f4bbd7cc51acb6467c86da806eebaa068fc9f69..cd0c5be8d01247e8bab3f4e00e6f171803291881
+++ b/fs/open.c
@@@ -193,8 -193,7 +193,8 @@@ static long do_sys_ftruncate(unsigned i
                goto out_putf;
  
        error = -EPERM;
 -      if (IS_APPEND(inode))
 +      /* Check IS_APPEND on real upper inode */
 +      if (IS_APPEND(file_inode(f.file)))
                goto out_putf;
  
        sb_start_write(inode->i_sb);
@@@ -460,20 -459,17 +460,17 @@@ out
  SYSCALL_DEFINE1(fchdir, unsigned int, fd)
  {
        struct fd f = fdget_raw(fd);
-       struct inode *inode;
-       int error = -EBADF;
+       int error;
  
        error = -EBADF;
        if (!f.file)
                goto out;
  
-       inode = file_inode(f.file);
        error = -ENOTDIR;
-       if (!S_ISDIR(inode->i_mode))
+       if (!d_can_lookup(f.file->f_path.dentry))
                goto out_putf;
  
-       error = inode_permission(inode, MAY_EXEC | MAY_CHDIR);
+       error = inode_permission(file_inode(f.file), MAY_EXEC | MAY_CHDIR);
        if (!error)
                set_fs_pwd(current->fs, &f.file->f_path);
  out_putf:
@@@ -901,12 -897,6 +898,12 @@@ static inline int build_open_flags(int 
        int lookup_flags = 0;
        int acc_mode = ACC_MODE(flags);
  
 +      /*
 +       * Clear out all open flags we don't know about so that we don't report
 +       * them in fcntl(F_GETFD) or similar interfaces.
 +       */
 +      flags &= VALID_OPEN_FLAGS;
 +
        if (flags & (O_CREAT | __O_TMPFILE))
                op->mode = (mode & S_IALLUGO) | S_IFREG;
        else
@@@ -1085,26 -1075,6 +1082,26 @@@ SYSCALL_DEFINE4(openat, int, dfd, cons
        return do_sys_open(dfd, filename, flags, mode);
  }
  
 +#ifdef CONFIG_COMPAT
 +/*
 + * Exactly like sys_open(), except that it doesn't set the
 + * O_LARGEFILE flag.
 + */
 +COMPAT_SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
 +{
 +      return do_sys_open(AT_FDCWD, filename, flags, mode);
 +}
 +
 +/*
 + * Exactly like sys_openat(), except that it doesn't set the
 + * O_LARGEFILE flag.
 + */
 +COMPAT_SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags, umode_t, mode)
 +{
 +      return do_sys_open(dfd, filename, flags, mode);
 +}
 +#endif
 +
  #ifndef __alpha__
  
  /*