Linux filesystem development
 help / color / mirror / Atom feed
* [PATCH 0/2] fuse: fix invalidate lock leaks on DAX truncate error paths
@ 2026-08-17 15:17 Baokun Li
  2026-08-17 15:18 ` [PATCH 1/2] fuse: fix invalidate lock leak on setattr writeback failure Baokun Li
  2026-08-17 15:18 ` [PATCH 2/2] fuse: fix invalidate lock leak on open O_TRUNC DAX failure Baokun Li
  0 siblings, 2 replies; 3+ messages in thread
From: Baokun Li @ 2026-08-17 15:17 UTC (permalink / raw)
  To: fuse-devel; +Cc: miklos, linux-fsdevel, linux-kernel, vgoyal, jefflexu

Baokun Li (2):
  fuse: fix invalidate lock leak on setattr writeback failure
  fuse: fix invalidate lock leak on open O_TRUNC DAX failure

 fs/fuse/dir.c  | 9 ++++-----
 fs/fuse/file.c | 4 ++--
 2 files changed, 6 insertions(+), 7 deletions(-)

-- 
2.43.7


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 1/2] fuse: fix invalidate lock leak on setattr writeback failure
  2026-08-17 15:17 [PATCH 0/2] fuse: fix invalidate lock leaks on DAX truncate error paths Baokun Li
@ 2026-08-17 15:18 ` Baokun Li
  2026-08-17 15:18 ` [PATCH 2/2] fuse: fix invalidate lock leak on open O_TRUNC DAX failure Baokun Li
  1 sibling, 0 replies; 3+ messages in thread
From: Baokun Li @ 2026-08-17 15:18 UTC (permalink / raw)
  To: fuse-devel; +Cc: miklos, linux-fsdevel, linux-kernel, vgoyal, jefflexu, stable

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>
---
 fs/fuse/dir.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 0e2a1039fa43..48763bc192f3 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -2161,10 +2161,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) {
@@ -2191,7 +2189,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);
@@ -2299,6 +2297,7 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 
 	clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state);
 
+unlock:
 	if (fault_blocked)
 		filemap_invalidate_unlock(mapping);
 	return err;
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 2/2] fuse: fix invalidate lock leak on open O_TRUNC DAX failure
  2026-08-17 15:17 [PATCH 0/2] fuse: fix invalidate lock leaks on DAX truncate error paths Baokun Li
  2026-08-17 15:18 ` [PATCH 1/2] fuse: fix invalidate lock leak on setattr writeback failure Baokun Li
@ 2026-08-17 15:18 ` Baokun Li
  1 sibling, 0 replies; 3+ messages in thread
From: Baokun Li @ 2026-08-17 15:18 UTC (permalink / raw)
  To: fuse-devel; +Cc: miklos, linux-fsdevel, linux-kernel, vgoyal, jefflexu, stable

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>
---
 fs/fuse/file.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index ceada75310b8..e7b2a839f081 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -272,7 +272,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)
@@ -296,9 +296,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);
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-08-17 15:18 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-17 15:17 [PATCH 0/2] fuse: fix invalidate lock leaks on DAX truncate error paths Baokun Li
2026-08-17 15:18 ` [PATCH 1/2] fuse: fix invalidate lock leak on setattr writeback failure Baokun Li
2026-08-17 15:18 ` [PATCH 2/2] fuse: fix invalidate lock leak on open O_TRUNC DAX failure Baokun Li

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox