Message ID | 20210905070833.201102-2-amir73il@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [5.10,1/2] new helper: inode_wrong_type() | expand |
On Sun, Sep 05, 2021 at 10:08:33AM +0300, Amir Goldstein wrote: > [ Upstream commit 15db16837a35d8007cb8563358787412213db25e ] > > Server responds to LOOKUP and other ops (READDIRPLUS/CREATE/MKNOD/...) > with ourarg 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. > > Stable backport notes: > * This is not a regression. The bug has been in fuse forever, but only > a certain class of low level fuse filesystems can trigger this bug > * Because there is no way to check if this fix is applied in runtime, > libfuse test_examples.py tests this fix with hardcoded check for > kernel version >= 5.14 > * After backport to stable kernel(s), the libfuse test can be updated > to also check minimal stable kernel version(s) > * Depends on "fuse: fix bad inode" which is already applied to stable > kernels v5.4.y and v5.10.y > * Required backporting helper inode_wrong_type() All now queued up, thanks! greg k-h
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 37d50dde845e..2e300176cb88 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -252,7 +252,7 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags) if (ret == -ENOMEM) goto out; if (ret || fuse_invalid_attr(&outarg.attr) || - inode_wrong_type(inode, outarg.attr.mode)) + fuse_stale_inode(inode, outarg.generation, &outarg.attr)) goto invalid; forget_all_cached_acls(inode); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 8150621101c6..ff94da684017 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -860,6 +860,13 @@ 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); +} + 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 6345c4679fb8..053c56af3b6f 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -340,8 +340,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;