--- /dev/null
+From 15db16837a35d8007cb8563358787412213db25e Mon Sep 17 00:00:00 2001
+From: Amir Goldstein <amir73il@gmail.com>
+Date: Mon, 21 Jun 2021 14:03:53 +0300
+Subject: fuse: fix illegal access to inode with reused nodeid
+
+From: Amir Goldstein <amir73il@gmail.com>
+
+commit 15db16837a35d8007cb8563358787412213db25e upstream.
+
+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()
+
+Signed-off-by: Amir Goldstein <amir73il@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/linux-fsdevel/CAOQ4uxi8DymG=JO_sAU+wS8akFdzh+PuXwW3Ebgahd2Nwnh7zA@mail.gmail.com/
+Signed-off-by: Amir Goldstein <amir73il@gmail.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ fs/fuse/dir.c | 2 +-
+ fs/fuse/fuse_i.h | 7 +++++++
+ fs/fuse/inode.c | 4 ++--
+ fs/fuse/readdir.c | 7 +++++--
+ 4 files changed, 15 insertions(+), 5 deletions(-)
+
+--- a/fs/fuse/dir.c
++++ b/fs/fuse/dir.c
+@@ -252,7 +252,7 @@ static int fuse_dentry_revalidate(struct
+ 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);
+--- a/fs/fuse/fuse_i.h
++++ b/fs/fuse/fuse_i.h
+@@ -860,6 +860,13 @@ static inline u64 fuse_get_attr_version(
+ 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);
+--- a/fs/fuse/inode.c
++++ b/fs/fuse/inode.c
+@@ -340,8 +340,8 @@ retry:
+ 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;
+--- a/fs/fuse/readdir.c
++++ b/fs/fuse/readdir.c
+@@ -200,9 +200,12 @@ retry:
+ 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;
--- /dev/null
+From 6e3e2c4362e41a2f18e3f7a5ad81bd2f49a47b85 Mon Sep 17 00:00:00 2001
+From: Al Viro <viro@zeniv.linux.org.uk>
+Date: Mon, 1 Mar 2021 20:37:10 -0500
+Subject: new helper: inode_wrong_type()
+
+From: Al Viro <viro@zeniv.linux.org.uk>
+
+commit 6e3e2c4362e41a2f18e3f7a5ad81bd2f49a47b85 upstream.
+
+inode_wrong_type(inode, mode) returns true if setting inode->i_mode
+to given value would've changed the inode type. We have enough of
+those checks open-coded to make a helper worthwhile.
+
+Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
+Signed-off-by: Amir Goldstein <amir73il@gmail.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/9p/vfs_inode.c | 4 ++--
+ fs/9p/vfs_inode_dotl.c | 4 ++--
+ fs/cifs/inode.c | 5 ++---
+ fs/fuse/dir.c | 6 +++---
+ fs/fuse/inode.c | 2 +-
+ fs/fuse/readdir.c | 2 +-
+ fs/nfs/inode.c | 6 +++---
+ fs/nfsd/nfsproc.c | 2 +-
+ fs/overlayfs/namei.c | 4 ++--
+ include/linux/fs.h | 5 +++++
+ 10 files changed, 22 insertions(+), 18 deletions(-)
+
+--- a/fs/9p/vfs_inode.c
++++ b/fs/9p/vfs_inode.c
+@@ -398,7 +398,7 @@ static int v9fs_test_inode(struct inode
+
+ umode = p9mode2unixmode(v9ses, st, &rdev);
+ /* don't match inode of different type */
+- if ((inode->i_mode & S_IFMT) != (umode & S_IFMT))
++ if (inode_wrong_type(inode, umode))
+ return 0;
+
+ /* compare qid details */
+@@ -1360,7 +1360,7 @@ int v9fs_refresh_inode(struct p9_fid *fi
+ * Don't update inode if the file type is different
+ */
+ umode = p9mode2unixmode(v9ses, st, &rdev);
+- if ((inode->i_mode & S_IFMT) != (umode & S_IFMT))
++ if (inode_wrong_type(inode, umode))
+ goto out;
+
+ /*
+--- a/fs/9p/vfs_inode_dotl.c
++++ b/fs/9p/vfs_inode_dotl.c
+@@ -59,7 +59,7 @@ static int v9fs_test_inode_dotl(struct i
+ struct p9_stat_dotl *st = (struct p9_stat_dotl *)data;
+
+ /* don't match inode of different type */
+- if ((inode->i_mode & S_IFMT) != (st->st_mode & S_IFMT))
++ if (inode_wrong_type(inode, st->st_mode))
+ return 0;
+
+ if (inode->i_generation != st->st_gen)
+@@ -933,7 +933,7 @@ int v9fs_refresh_inode_dotl(struct p9_fi
+ /*
+ * Don't update inode if the file type is different
+ */
+- if ((inode->i_mode & S_IFMT) != (st->st_mode & S_IFMT))
++ if (inode_wrong_type(inode, st->st_mode))
+ goto out;
+
+ /*
+--- a/fs/cifs/inode.c
++++ b/fs/cifs/inode.c
+@@ -425,8 +425,7 @@ int cifs_get_inode_info_unix(struct inod
+ }
+
+ /* if filetype is different, return error */
+- if (unlikely(((*pinode)->i_mode & S_IFMT) !=
+- (fattr.cf_mode & S_IFMT))) {
++ if (unlikely(inode_wrong_type(*pinode, fattr.cf_mode))) {
+ CIFS_I(*pinode)->time = 0; /* force reval */
+ rc = -ESTALE;
+ goto cgiiu_exit;
+@@ -1243,7 +1242,7 @@ cifs_find_inode(struct inode *inode, voi
+ return 0;
+
+ /* don't match inode of different type */
+- if ((inode->i_mode & S_IFMT) != (fattr->cf_mode & S_IFMT))
++ if (inode_wrong_type(inode, fattr->cf_mode))
+ return 0;
+
+ /* if it's not a directory or has no dentries, then flag it */
+--- a/fs/fuse/dir.c
++++ b/fs/fuse/dir.c
+@@ -252,7 +252,7 @@ static int fuse_dentry_revalidate(struct
+ if (ret == -ENOMEM)
+ goto out;
+ if (ret || fuse_invalid_attr(&outarg.attr) ||
+- (outarg.attr.mode ^ inode->i_mode) & S_IFMT)
++ inode_wrong_type(inode, outarg.attr.mode))
+ goto invalid;
+
+ forget_all_cached_acls(inode);
+@@ -1062,7 +1062,7 @@ static int fuse_do_getattr(struct inode
+ err = fuse_simple_request(fm, &args);
+ if (!err) {
+ if (fuse_invalid_attr(&outarg.attr) ||
+- (inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
++ inode_wrong_type(inode, outarg.attr.mode)) {
+ fuse_make_bad(inode);
+ err = -EIO;
+ } else {
+@@ -1699,7 +1699,7 @@ int fuse_do_setattr(struct dentry *dentr
+ }
+
+ if (fuse_invalid_attr(&outarg.attr) ||
+- (inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
++ inode_wrong_type(inode, outarg.attr.mode)) {
+ fuse_make_bad(inode);
+ err = -EIO;
+ goto error;
+--- a/fs/fuse/inode.c
++++ b/fs/fuse/inode.c
+@@ -340,7 +340,7 @@ retry:
+ inode->i_generation = generation;
+ fuse_init_inode(inode, attr);
+ unlock_new_inode(inode);
+- } else if ((inode->i_mode ^ attr->mode) & S_IFMT) {
++ } else if (inode_wrong_type(inode, attr->mode)) {
+ /* Inode has changed type, any I/O on the old should fail */
+ fuse_make_bad(inode);
+ iput(inode);
+--- a/fs/fuse/readdir.c
++++ b/fs/fuse/readdir.c
+@@ -202,7 +202,7 @@ retry:
+ inode = d_inode(dentry);
+ if (!inode ||
+ get_node_id(inode) != o->nodeid ||
+- ((o->attr.mode ^ inode->i_mode) & S_IFMT)) {
++ inode_wrong_type(inode, o->attr.mode)) {
+ d_invalidate(dentry);
+ dput(dentry);
+ goto retry;
+--- a/fs/nfs/inode.c
++++ b/fs/nfs/inode.c
+@@ -322,7 +322,7 @@ nfs_find_actor(struct inode *inode, void
+
+ if (NFS_FILEID(inode) != fattr->fileid)
+ return 0;
+- if ((S_IFMT & inode->i_mode) != (S_IFMT & fattr->mode))
++ if (inode_wrong_type(inode, fattr->mode))
+ return 0;
+ if (nfs_compare_fh(NFS_FH(inode), fh))
+ return 0;
+@@ -1446,7 +1446,7 @@ static int nfs_check_inode_attributes(st
+ return 0;
+ return -ESTALE;
+ }
+- if ((fattr->valid & NFS_ATTR_FATTR_TYPE) && (inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT))
++ if ((fattr->valid & NFS_ATTR_FATTR_TYPE) && inode_wrong_type(inode, fattr->mode))
+ return -ESTALE;
+
+
+@@ -1861,7 +1861,7 @@ static int nfs_update_inode(struct inode
+ /*
+ * Make sure the inode's type hasn't changed.
+ */
+- if ((fattr->valid & NFS_ATTR_FATTR_TYPE) && (inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT)) {
++ if ((fattr->valid & NFS_ATTR_FATTR_TYPE) && inode_wrong_type(inode, fattr->mode)) {
+ /*
+ * Big trouble! The inode has become a different object.
+ */
+--- a/fs/nfsd/nfsproc.c
++++ b/fs/nfsd/nfsproc.c
+@@ -376,7 +376,7 @@ nfsd_proc_create(struct svc_rqst *rqstp)
+
+ /* Make sure the type and device matches */
+ resp->status = nfserr_exist;
+- if (inode && type != (inode->i_mode & S_IFMT))
++ if (inode && inode_wrong_type(inode, type))
+ goto out_unlock;
+ }
+
+--- a/fs/overlayfs/namei.c
++++ b/fs/overlayfs/namei.c
+@@ -366,7 +366,7 @@ int ovl_check_origin_fh(struct ovl_fs *o
+ return PTR_ERR(origin);
+
+ if (upperdentry && !ovl_is_whiteout(upperdentry) &&
+- ((d_inode(origin)->i_mode ^ d_inode(upperdentry)->i_mode) & S_IFMT))
++ inode_wrong_type(d_inode(upperdentry), d_inode(origin)->i_mode))
+ goto invalid;
+
+ if (!*stackp)
+@@ -724,7 +724,7 @@ struct dentry *ovl_lookup_index(struct o
+ index = ERR_PTR(-ESTALE);
+ goto out;
+ } else if (ovl_dentry_weird(index) || ovl_is_whiteout(index) ||
+- ((inode->i_mode ^ d_inode(origin)->i_mode) & S_IFMT)) {
++ inode_wrong_type(inode, d_inode(origin)->i_mode)) {
+ /*
+ * Index should always be of the same file type as origin
+ * except for the case of a whiteout index. A whiteout
+--- a/include/linux/fs.h
++++ b/include/linux/fs.h
+@@ -2768,6 +2768,11 @@ static inline bool execute_ok(struct ino
+ return (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode);
+ }
+
++static inline bool inode_wrong_type(const struct inode *inode, umode_t mode)
++{
++ return (inode->i_mode ^ mode) & S_IFMT;
++}
++
+ static inline void file_start_write(struct file *file)
+ {
+ if (!S_ISREG(file_inode(file)->i_mode))