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

fuse_open() takes filemap_invalidate_lock() for a DAX truncate
(dax_truncate = true) and releases it before the out_inode_unlock
label.  But when fuse_dax_break_layouts() fails, the goto
out_inode_unlock skips the unlock and leaks the rwsem, so any later
fault or truncate on the file stalls on the stale lock.

fuse_dax_break_layouts() can fail with -ERESTARTSYS when a signal
interrupts the wait for busy DAX pages to drain:

  open("file", O_RDWR | O_TRUNC)
  └─ fuse_open()
     ├─ filemap_invalidate_lock()        # dax_truncate
     └─ fuse_dax_break_layouts()
        └─ dax_break_layout()
           └─ wait_page_idle()           # TASK_INTERRUPTIBLE
              └─ fuse_wait_dax_page()    # unlock, schedule, re-lock
                 └─ signal → -ERESTARTSYS
     goto out_inode_unlock               # <- lock leaked

Fix this by moving filemap_invalidate_unlock() below the label so
that all error paths release the lock, and rename the label to
out_unlock as it now covers more than just the inode lock.

Fixes: 2fdbb8dd0155 ("fuse: fix deadlock between atomic O_TRUNC and page invalidation")
Cc: stable@vger.kernel.org # v6.0+
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/file.c

index 585dd90361b65493533c339ae22ef2ea539953d9..c581cd1df3e27305c02ff9b238dee81685f52301 100644 (file)
@@ -268,7 +268,7 @@ static int fuse_open(struct inode *inode, struct file *file)
                filemap_invalidate_lock(inode->i_mapping);
                err = fuse_dax_break_layouts(inode, 0, -1);
                if (err)
-                       goto out_inode_unlock;
+                       goto out_unlock;
        }
 
        if (is_wb_truncate || dax_truncate)
@@ -292,9 +292,9 @@ static int fuse_open(struct inode *inode, struct file *file)
                else if (!(ff->open_flags & FOPEN_KEEP_CACHE))
                        invalidate_inode_pages2(inode->i_mapping);
        }
+out_unlock:
        if (dax_truncate)
                filemap_invalidate_unlock(inode->i_mapping);
-out_inode_unlock:
        if (is_wb_truncate || dax_truncate)
                inode_unlock(inode);