[PATCH v2] ocfs2: fix hung task in orphan recovery

From: Jiaming Zhang

Date: Thu Jul 02 2026 - 05:14:09 EST


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@xxxxxxxxxxxxxx/
Assisted-by: Codex:gpt-5.5-xhigh
Signed-off-by: Jiaming Zhang <r772577952@xxxxxxxxx>
---
Changes in v2:
- Propagate the actual error from ocfs2_empty_dir() to caller.
- Update ocfs2_unlink() and ocfs2_rename() to distinguish errors from a
non-empty directory result.

fs/ocfs2/dir.c | 20 ++++++++++++++------
fs/ocfs2/namei.c | 11 ++++++++---
2 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
index 8e6b03238327..baf3eca7b4e4 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;

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;
}

@@ -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);
}

/*
@@ -2167,7 +2171,7 @@ static int ocfs2_empty_dir_dx(struct inode *inode,
/*
* routine to check that the specified directory is empty (for rmdir)
*
- * Returns 1 if dir is empty, zero otherwise.
+ * Returns 1 if dir is empty, zero if not, and a negative errno on error.
*
* XXX: This is a performance problem for unindexed directories.
*/
@@ -2180,8 +2184,10 @@ int ocfs2_empty_dir(struct inode *inode)

if (ocfs2_dir_indexed(inode)) {
ret = ocfs2_empty_dir_dx(inode, &priv);
- if (ret)
+ if (ret) {
mlog_errno(ret);
+ return ret;
+ }
/*
* We still run ocfs2_dir_foreach to get the checks
* for "." and "..".
@@ -2189,8 +2195,10 @@ int ocfs2_empty_dir(struct inode *inode)
}

ret = ocfs2_dir_foreach(inode, &priv.ctx);
- if (ret)
+ if (ret) {
mlog_errno(ret);
+ return ret;
+ }

if (!priv.seen_dot || !priv.seen_dot_dot) {
mlog(ML_ERROR, "bad directory (dir #%llu) - no `.' or `..'\n",
diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
index 1277666c77cd..abce59e1f180 100644
--- a/fs/ocfs2/namei.c
+++ b/fs/ocfs2/namei.c
@@ -945,7 +945,10 @@ static int ocfs2_unlink(struct inode *dir,
child_locked = 1;

if (S_ISDIR(inode->i_mode)) {
- if (inode->i_nlink != 2 || !ocfs2_empty_dir(inode)) {
+ status = ocfs2_empty_dir(inode);
+ if (status < 0)
+ goto leave;
+ if (inode->i_nlink != 2 || !status) {
status = -ENOTEMPTY;
goto leave;
}
@@ -1499,8 +1502,10 @@ static int ocfs2_rename(struct mnt_idmap *idmap,

if (target_exists) {
if (S_ISDIR(new_inode->i_mode)) {
- if (new_inode->i_nlink != 2 ||
- !ocfs2_empty_dir(new_inode)) {
+ status = ocfs2_empty_dir(new_inode);
+ if (status < 0)
+ goto bail;
+ if (new_inode->i_nlink != 2 || !status) {
status = -ENOTEMPTY;
goto bail;
}
--
2.43.0