[PATCH] ocfs2: fix hung task in orphan recovery

Jiaming Zhang <[email protected]> Wed, 1 Jul 2026 17:16:13 +0800
Newsgroups dev.linux.lists.ocfs2-devel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
A crafted OCFS2 image with corrupted orphan-directory extent metadata can make
umount hang.

During unmount, ocfs2_recovery_disable() waits for the ocfs2_complete_recovery
work item to finish. The worker scans the orphan directory through
ocfs2_queue_orphans() and ocfs2_dir_foreach(). If ocfs2_read_dir_block() fails
on a corrupted directory block, ocfs2_dir_foreach_blk_el() skips the block and
continues walking. On a badly corrupted directory this can keep orphan recovery
busy for a long time, leaving umount blocked while flushing osb->ocfs2_wq.

Return the read error immediately for full directory scans and propagate the
error from ocfs2_dir_foreach(). When ocfs2_empty_dir() receives such an error,
report the directory as non-empty so unlink/rmdir does not proceed on an
unreadable directory.

Closes: https://lore.kernel.org/lkml/CANypQFbWH76Y6LWHEwAvTP7aQL04uMJ=dDyL6YDmxa3fv3Tyjg@mail.gmail.com/
Assisted-by: Codex:gpt-5.5-xhigh
Signed-off-by: Jiaming Zhang <[email protected]>
---
 fs/ocfs2/dir.c | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
index 8e6b03238327..9bebf28f4b77 100644
--- a/fs/ocfs2/dir.c
+++ b/fs/ocfs2/dir.c
@@ -1866,6 +1866,7 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
 	struct super_block * sb = inode->i_sb;
 	unsigned int ra_sectors = 16;
 	int stored = 0;
+	int ret = 0;
 
 	bh = NULL;
 
@@ -1873,9 +1874,13 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
 
 	while (ctx->pos < i_size_read(inode)) {
 		blk = ctx->pos >> sb->s_blocksize_bits;
-		if (ocfs2_read_dir_block(inode, blk, &bh, 0)) {
+		ret = ocfs2_read_dir_block(inode, blk, &bh, 0);
+		if (ret) {
+			if (persist)
+				return ret;
 			/* Skip the corrupt dirblock and keep trying */
 			ctx->pos += sb->s_blocksize - offset;
+			offset = 0;
 			continue;
 		}
 
@@ -1950,7 +1955,7 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
 		if (!persist && stored)
 			break;
 	}
-	return 0;
+	return ret;
 }
 
 static int ocfs2_dir_foreach_blk(struct inode *inode, u64 *f_version,
@@ -1969,8 +1974,7 @@ static int ocfs2_dir_foreach_blk(struct inode *inode, u64 *f_version,
 int ocfs2_dir_foreach(struct inode *inode, struct dir_context *ctx)
 {
 	u64 version = inode_query_iversion(inode);
-	ocfs2_dir_foreach_blk(inode, &version, ctx, true);
-	return 0;
+	return ocfs2_dir_foreach_blk(inode, &version, ctx, true);
 }
 
 /*
@@ -2189,8 +2193,10 @@ int ocfs2_empty_dir(struct inode *inode)
 	}
 
 	ret = ocfs2_dir_foreach(inode, &priv.ctx);
-	if (ret)
+	if (ret) {
 		mlog_errno(ret);
+		return 0;
+	}
 
 	if (!priv.seen_dot || !priv.seen_dot_dot) {
 		mlog(ML_ERROR, "bad directory (dir #%llu) - no `.' or `..'\n",
-- 
2.43.0