]> git.hungrycats.org Git - linux/commitdiff
fuse: fix invalidate lock leak on setattr writeback failure
authorBaokun Li <libaokun@linux.alibaba.com>
Mon, 17 Aug 2026 15:18:00 +0000 (23:18 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 2 Sep 2026 12:31:48 +0000 (14:31 +0200)
commit 9afeca0d569c9fc89d758fe7a9339d1e8afb1546 upstream.

fuse_do_setattr() takes filemap_invalidate_lock() for a DAX truncate
(fault_blocked = true) and releases it at the out:/error: labels.  But
when a writeback flush is also needed, a write_inode_now() failure
returns directly and leaks the lock, so any later fault or truncate on
the file stalls on the stale rwsem.

For example, truncate(2) on a setuid file reaches fuse_do_setattr()
with both ATTR_SIZE and ATTR_MODE set:

  truncate(2)
  └─ do_truncate()
     ├─ dentry_needs_remove_privs()         # S_ISUID
     └─ notify_change()                     # KILL_SUID -> ATTR_MODE
        └─ fuse_setattr()                   # no killpriv:
           │                                #   ia_valid |= ATTR_MODE
           └─ fuse_do_setattr()
              ├─ filemap_invalidate_lock()  # IS_DAX && is_truncate
              └─ write_inode_now()          # is_wb && ATTR_MODE
                 └─ if (err)                # e.g. daemon -> -EIO
                    return err              # <- lock leaked

Fix this by adding an unlock label that releases the lock before
returning the error, and use it for the fuse_dax_break_layouts()
failure path as well.

Fixes: 6ae330cad6ef ("virtiofs: serialize truncate/punch_hole and dax fault path")
Cc: stable@vger.kernel.org # v5.10+
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/fuse/dir.c

index 1bc6982b5d6aa5959a3525c1f32a3201c929d5b6..a013fad843b2cdbb55827ca1de07f78b2a9ddccc 100644 (file)
@@ -1976,10 +1976,8 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
                filemap_invalidate_lock(mapping);
                fault_blocked = true;
                err = fuse_dax_break_layouts(inode, 0, -1);
-               if (err) {
-                       filemap_invalidate_unlock(mapping);
-                       return err;
-               }
+               if (err)
+                       goto unlock;
        }
 
        if (attr->ia_valid & ATTR_OPEN) {
@@ -2006,7 +2004,7 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
                         ATTR_TIMES_SET)) {
                err = write_inode_now(inode, true);
                if (err)
-                       return err;
+                       goto unlock;
 
                fuse_set_nowrite(inode);
                fuse_release_nowrite(inode);
@@ -2114,6 +2112,7 @@ error:
 
        clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state);
 
+unlock:
        if (fault_blocked)
                filemap_invalidate_unlock(mapping);
        return err;