Re: [PATCH] fuse: fix illegal access to inode with reused nodeid

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

 



On Wed, Jun 9, 2021 at 9:12 PM Amir Goldstein <amir73il@xxxxxxxxx> wrote:
>
> Server responds to LOOKUP and other ops (READDIRPLUS/CREATE/MKNOD/...)
> with outarg containing nodeid and generation.
>
> If a fuse inode is found in inode cache with the same nodeid but
> different generation, the existing fuse inode should be unhashed and
> marked "bad" and a new inode with the new generation should be hashed
> instead.
>
> This can happen, for example, with passhrough fuse filesystem that
> returns the real filesystem ino/generation on lookup and where real inode
> numbers can get recycled due to real files being unlinked not via the fuse
> passthrough filesystem.
>
> With current code, this situation will not be detected and an old fuse
> dentry that used to point to an older generation real inode, can be used
> to access a completely new inode, which should be accessed only via the
> new dentry.
>
> Note that because the FORGET message carries the nodeid w/o generation,
> the server should wait to get FORGET counts for the nlookup counts of
> the old and reused inodes combined, before it can free the resources
> associated to that nodeid.
>
> Link: https://lore.kernel.org/linux-fsdevel/CAOQ4uxgDMGUpK35huwqFYGH_idBB8S6eLiz85o0DDKOyDH4Syg@xxxxxxxxxxxxxx/
> Signed-off-by: Amir Goldstein <amir73il@xxxxxxxxx>
> ---
>
> Miklos,
>
> I was able to reproduce this issue with a passthrough fs that stored
> ino+generation and uses then to open fd on lookup.
>
> I extended libfuse's test_syscalls [1] program to demonstrate the issue
> described in commit message.
>
> Max, IIUC, you are making a modification to virtiofs-rs that would
> result is being exposed to this bug.  You are welcome to try out the
> test and let me know if you can reproduce the issue.
>
> Note that some test_syscalls test fail with cache enabled, so libfuse's
> test_examples.py only runs test_syscalls in cache disabled config.
>
> Thanks,
> Amir.
>
> [1] https://github.com/amir73il/libfuse/commits/test-reused-inodes
>

Miklos,

Not sure if you got to look at this already, but I had noticed that the
link above is broken because I deleted the branch after Nikolaus
merged it to upstream libfuse, so here is a new link to the PR [2]
with some more relevant context.

Per request from Nikolaus, I modified the passthrough_hp example
to reuse inodes on last close+unlink, so it now hits the failure in the
new test with upstream kernel and it passes the test with this kernel fix.

Thanks,
Amir.

[2] https://github.com/libfuse/libfuse/pull/612


>  fs/fuse/dir.c     | 3 ++-
>  fs/fuse/fuse_i.h  | 9 +++++++++
>  fs/fuse/inode.c   | 4 ++--
>  fs/fuse/readdir.c | 7 +++++--
>  4 files changed, 18 insertions(+), 5 deletions(-)
>
> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> index 1b6c001a7dd1..b06628fd7d8e 100644
> --- a/fs/fuse/dir.c
> +++ b/fs/fuse/dir.c
> @@ -239,7 +239,8 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
>                 if (!ret) {
>                         fi = get_fuse_inode(inode);
>                         if (outarg.nodeid != get_node_id(inode) ||
> -                           (bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) {
> +                           fuse_stale_inode(inode, outarg.generation,
> +                                            &outarg.attr)) {
>                                 fuse_queue_forget(fm->fc, forget,
>                                                   outarg.nodeid, 1);
>                                 goto invalid;
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 7e463e220053..f1bd28c176a9 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -867,6 +867,15 @@ static inline u64 fuse_get_attr_version(struct fuse_conn *fc)
>         return atomic64_read(&fc->attr_version);
>  }
>
> +static inline bool fuse_stale_inode(const struct inode *inode, int generation,
> +                                   struct fuse_attr *attr)
> +{
> +       return inode->i_generation != generation ||
> +               inode_wrong_type(inode, attr->mode) ||
> +               (bool) IS_AUTOMOUNT(inode) !=
> +               (bool) (attr->flags & FUSE_ATTR_SUBMOUNT);
> +}
> +
>  static inline void fuse_make_bad(struct inode *inode)
>  {
>         remove_inode_hash(inode);
> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> index 393e36b74dc4..257bb3e1cac8 100644
> --- a/fs/fuse/inode.c
> +++ b/fs/fuse/inode.c
> @@ -350,8 +350,8 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>                 inode->i_generation = generation;
>                 fuse_init_inode(inode, attr);
>                 unlock_new_inode(inode);
> -       } else if (inode_wrong_type(inode, attr->mode)) {
> -               /* Inode has changed type, any I/O on the old should fail */
> +       } else if (fuse_stale_inode(inode, generation, attr)) {
> +               /* nodeid was reused, any I/O on the old inode should fail */
>                 fuse_make_bad(inode);
>                 iput(inode);
>                 goto retry;
> diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c
> index 277f7041d55a..bc267832310c 100644
> --- a/fs/fuse/readdir.c
> +++ b/fs/fuse/readdir.c
> @@ -200,9 +200,12 @@ static int fuse_direntplus_link(struct file *file,
>         if (!d_in_lookup(dentry)) {
>                 struct fuse_inode *fi;
>                 inode = d_inode(dentry);
> +               if (inode && get_node_id(inode) != o->nodeid)
> +                       inode = NULL;
>                 if (!inode ||
> -                   get_node_id(inode) != o->nodeid ||
> -                   inode_wrong_type(inode, o->attr.mode)) {
> +                   fuse_stale_inode(inode, o->generation, &o->attr)) {
> +                       if (inode)
> +                               fuse_make_bad(inode);
>                         d_invalidate(dentry);
>                         dput(dentry);
>                         goto retry;
> --
> 2.31.1
>



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

  Powered by Linux