Re: [PATCH v2] ksmbd: fix racy issue from using ->d_parent and ->d_name

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



2022년 2월 18일 (금) 오후 5:02, Namjae Jeon <linkinjeon@xxxxxxxxxx>님이 작성:
>
> Hi Al,
> 2022-02-18 8:41 GMT+09:00, Al Viro <viro@xxxxxxxxxxxxxxxxxx>:
> > On Thu, Feb 17, 2022 at 08:03:19AM +0900, Namjae Jeon wrote:
> >> Al pointed out that ksmbd has racy issue from using ->d_parent and
> >> ->d_name
> >> in ksmbd_vfs_unlink and smb2_vfs_rename(). and he suggested changing from
> >> the way it start with dget_parent(), which can cause retry loop and
> >> unexpected errors, to find the parent of child, lock it and then look for
> >> a child in locked directory.
> >>
> >> This patch introduce a new helper(vfs_path_parent_lookup()) to avoid
> >> out of share access and export vfs functions like the following ones to
> >> use
> >> vfs_path_parent_lookup() and filename_parentat().
> >>  - __lookup_hash().
> >>  - getname_kernel() and putname().
> >>  - filename_parentat()
> >
> > First of all, your vfs_path_parent_lookup() calling conventions are wrong.
> > You have 3 callers:
> >       err = vfs_path_parent_lookup(share->vfs_path.dentry,
> >                                    share->vfs_path.mnt, filename_struct,
> >                                    LOOKUP_NO_SYMLINKS | LOOKUP_BENEATH,
> >                                    &path, &last, &type);
> >       err = vfs_path_parent_lookup(share_conf->vfs_path.dentry,
> >                                    share_conf->vfs_path.mnt, to,
> >                                    lookup_flags | LOOKUP_BENEATH,
> >                                    &new_path, &new_last, &new_type);
> >       err = vfs_path_parent_lookup(share->vfs_path.dentry,
> >                                    share->vfs_path.mnt, filename_struct,
> >                                    LOOKUP_NO_SYMLINKS | LOOKUP_BENEATH,
> >                                    &path, &last, &type);
> > Note that in all of them the first two arguments come from ->dentry and
> > ->mnt of the same struct path instance.  Now, look at the function itself:
> >
> > int vfs_path_parent_lookup(struct dentry *dentry, struct vfsmount *mnt,
> >                          struct filename *filename, unsigned int flags,
> >                          struct path *parent, struct qstr *last, int *type)
> > {
> >       struct path root = {.mnt = mnt, .dentry = dentry};
> >
> >       return  __filename_parentat(AT_FDCWD, filename, flags, parent, last,
> >                                   type, &root);
> > }
> >
> > What about the __filename_parentat() last argument?  It's declared as
> > struct path *root and passed to set_nameidata().  No other uses.  And
> > set_nameidata() gets it via const struct path *root argument.  IOW,
> > it's not going to modify the contents of that struct path.  Since
> > you __filename_parentat() doesn't do anything else with its root
> > argument, there's no reason not to make _that_ const struct path *,
> > is there?
> Yep, No reason.
>
> >
> > Now, if you do that, you can safely turn vfs_path_parent_lookup()
> > take const struct path * instead of dentry/vfsmount pair of arguments
> > and drop the local struct path instance in the vfs_path_parent_lookup()
> > itself.
> Yes. I will change it.
>
> >
> > The fact that vfs_path_lookup() passes vfsmount and dentry separately
> > doesn't mean you need to do the same - look at the existing callers
> > of vfs_path_lookup() (outside of ksmbd itself) and you'll see the
> > difference.  Incidentally, this
> > fs/ksmbd/vfs.c:22:#include "../internal.h"      /* for vfs_path_lookup */
> > had been a really bad idea.  And no, nfsd doing the same is not a good
> > thing either...
> >
> > General rule: if it's exported, it's *NOT* internal.
> Okay. Then as another patch, I will move vfs_path_lookup prototype in
> internal.h to linux/namei.h.
>
> >
> >
> > Next:
> >
> >> index 077b8761d099..b094cd1d4951 100644
> >> --- a/fs/ksmbd/oplock.c
> >> +++ b/fs/ksmbd/oplock.c
> >> @@ -1713,11 +1713,14 @@ int smb2_check_durable_oplock(struct ksmbd_file
> >> *fp,
> >>                      ret = -EBADF;
> >>                      goto out;
> >>              }
> >> +            down_read(&fp->filename_lock);
> >>              if (name && strcmp(fp->filename, name)) {
> >> +                    up_read(&fp->filename_lock);
> >>                      pr_err("invalid name reconnect %s\n", name);
> >>                      ret = -EINVAL;
> >>                      goto out;
> >>              }
> >> +            up_read(&fp->filename_lock);
> >
> > What assumptions do you make about those strings?  Note that opened file
> > is *NOT* guaranteed to have its pathname remain unchanged - having
> > /tmp/foo/bar/baz/blah opened will not prevent mv /tmp/foo /tmp/barf
> > and the file will remain opened (and working just fine).  AFAICS, you
> > only update it in smb2_rename(), which is not going to be called by
> > mv(1) called by admin on server.
> Whenever a FILE_ALL_INFORMATION request is received from a client,
> ksmbd need to call d_path(then, removing the share path in pathname is
> required) to obtain pathname for windows. To avoid the issue you
> mentioned, we can remove the all uses of ->filename and calling
> d_path() whenever pathname is need.
>
> >
> > BTW, while grepping through the related code, convert_to_nt_pathname()
> > is Not Nice(tm).  Seriously, strlen(s) == 0 is not an idiomatic way to
> > check that s is an empty string.  What's more, return value of that
> > function ends up passed to kfree().  Which is not a good thing to do
> > to a string constant.  That can be recovered by use of kfree_const() in
> > get_file_all_info(), but.. ouch.
> Okay.
>
> >
> > ksmbd_vfs_rename(): UGH.
> >       * you allocate a buffer
> >       * do d_path() into it
> >       * then use getname_kernel() to allocate another one and copy the contents
> > into it.  By that point the string might have nothing to do with the actual
> > location of object, BTW (see above)
> Can we use dget_parent() and take_dentry_name_snapshot() for source
> file instead of d_path(), getname_kernel() and filename_parentat()?
> Because ksmbd receive fileid(ksmbd_file) for source file from client.
> [See control flow the below]
>

As Namjae said, for the rename, a client sends FileId of a source file and
an absolute path of a destination file. ksmbd can only get a dentry of
the source file from FileId.
So I think we can use dget_parent() to get a parent dentry. And we
have to verify the parent dentry by locking the parent inode, and
calling take_dentry_name_snapshot() and lookup_one().

> >       * then you use filename_parentat() (BTW, the need to export both it and
> > vfs_path_parent_lookup() is an artefact of bad calling conventions -
> > passing
> > NULL as const struct path * would do the right thing, if not for the fact
> > that
> > with your calling conventions you have to pass a non-NULL pointer - that to
> > a local struct path in your vfs_path_parent_lookup()).
> Okay.
>
> >       * then you use vfs_path_parent_lookup() to find the new parent.  OK,
> > but...
> > you proceed to check if it has somehow returned you a symlink.  Huh?  How
> > does
> > one get a symlink from path_parentat() or anything that would use it?
> As security issues, We made it not to allow symlinks.
>
> > I would very much appreciate a reproducer for that.
> >       * you use lock_rename() to lock both parents.  Which relies upon the
> > caller having checked that they live on the same filesystem.  Neither old
> > nor
> > new version do that, which means relatively easy deadlocks.
> Okay. will add the check for this.
>
> >       * look the last components up.  NB: the old one might very well have
> > nothing to do with the path.dentry.
> Okay.
>
> >       * do usual checks re loop prevention (with slightly unusual error
> > values, but whatever)
> I understood that you pointed out to add retry_estale() check and retry.
>
> >       * call ksmbd_lookup_fd_inode() on the old parent.  Then dereference
> > the return value (if non-NULL)... and never do anything else to it.  How
> > can
> > that possibly work?  What's there to prevent freeing of that struct
> > ksmbd_file
> > just as ksmbd_lookup_fd_inode() returns it?  Looks like it's either a leak
> > or
> > use-after-free, and looking at ksmbd_lookup_fd_inode() it's probably the
> > latter.
> Right. Need to increase reference count of ksmbd_file. I will fix it.
>
> >       * proceed with vfs_rename(), drop the stuff you'd acquired and go
> > away.
> Okay.
>
> >
> > ksmbd_vfs_unlink():
> >       * const char *filename, please, unless you really modify it there.
> Okay.
> >       * what the hell is that ihold/iput pair for?
> I refered codes in do_unlinkat() for this. If this pair is not needed,
> I'll delete it,
> but could you please tell me why it's needed in unlinkat() ?
>
> >
> > I'm not sure that the set you'd exported is the right one, but that's
> > secondary - I'd really like to understand what assumptions are you
> > making about the ->filename contents, as well as the control flow
> > from protocol request that demands rename to the actual call of
> > vfs_rename().  Could you elaborate on that?  I am not familiar with
> > the protocol, other than bits and pieces I'd observed in fs/cifs
> > code.
> As I said above, the uses of ->filename can be replaced with d_path().
> control flow for rename is the following.
>
>  a. Receiving smb2 create(open) request from client.
>      - Getting struct file after calling vfs_path_lookup() and
> dentry_open() using pathname from client.
>      - create ksmbd_file and add struct file to fp->filp and generate
> fileid for struct ksmbd_file.
>      - send smb2 create response included fileid of ksmbd_file to client.
>  b. Receiving smb2_set_info file(FILE_RENAME_INFORMATION) request from client.
>      - lookup ksmbd_file using fileid of request. This will source
> file for rename.
>      - get absolute pathname for destination fille in smb2 set info
> file for rename.
>      - find both parents of source and destination and lock and do rename...
>  c. Receiving smb2 close.
>
> Thanks for your review!
> >



-- 
Thanks,
Hyunchul




[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [NTFS 3]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [NTFS 3]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux