Linux XFS filesystem development
 help / color / mirror / Atom feed
From: "Darrick J. Wong" <djwong@kernel.org>
To: Michael Bommarito <michael.bommarito@gmail.com>
Cc: Carlos Maiolino <cem@kernel.org>,
	linux-xfs@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] xfs: detect cycles in recovered unlinked inode lists
Date: Mon, 18 May 2026 22:02:35 -0700	[thread overview]
Message-ID: <20260519050235.GI9568@frogsfrogsfrogs> (raw)
In-Reply-To: <20260518013143.1532327-1-michael.bommarito@gmail.com>

On Sun, May 17, 2026 at 09:31:43PM -0400, Michael Bommarito wrote:
> Two XFS walkers follow on-disk AGI unlinked bucket lists until the
> NULLAGINO sentinel and trust the links unconditionally:
> 
>   xlog_recover_iunlink_bucket(), reached at mount time when the log
>   needs recovery, via xlog_recover_finish() ->
>   xlog_recover_process_iunlinks().
> 
>   xfs_inode_reload_unlinked_bucket(), reached post-mount via
>   xfs_bulkstat_one_int() and other inode-reload paths.
> 
> A crafted image with an AGI bucket head pointing into a cycle traps
> either walker forever. With the mount-time walker, the mount(8)
> syscall itself never returns; the kernel thread is uninterruptible
> inside the loop, so SIGKILL on the mount process is queued but never
> delivered. With the bulkstat walker, an XFS_IOC_BULKSTAT call hangs
> the same way.
> 
> Reject invalid AG inode numbers, bucket mismatches, and repeated AG
> inode numbers in both walkers. Mark the AGI sick and return
> -EFSCORRUPTED so callers handle the corrupt metadata instead of
> walking the same on-disk list forever.
> 
> Reproduced on a crafted image whose AGI bucket holds a self-cycle and
> whose on-disk log is dirty: a stock kernel hangs in the mount syscall
> indefinitely, while the patched kernel completes log recovery,
> reports the corruption, and the filesystem shuts down with
> -EFSCORRUPTED.

(Does xfs_repair fix the problem if the mount fails?)

> Fixes: 04755d2e5821b3afbaadd09fe5df58d04de36484 ("xfs: refactor xlog_recover_process_iunlinks()")
> Fixes: 83771c50e42b92de6740a63e152c96c052d37736 ("xfs: reload entire unlinked bucket lists")
> Assisted-by: Codex:gpt-5-5-xhigh
> Assisted-by: Claude:claude-opus-4-7
> Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
> ---
>  fs/xfs/xfs_inode.c       | 29 +++++++++++++++++++++++++++++
>  fs/xfs/xfs_log_recover.c | 26 +++++++++++++++++++++++++-
>  2 files changed, 54 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
> index beaa26ec62da4..f930d5c823c77 100644
> --- a/fs/xfs/xfs_inode.c
> +++ b/fs/xfs/xfs_inode.c
> @@ -4,6 +4,7 @@
>   * All Rights Reserved.
>   */
>  #include <linux/iversion.h>
> +#include <linux/xarray.h>
>  
>  #include "xfs_platform.h"
>  #include "xfs_fs.h"
> @@ -2859,6 +2860,7 @@ xfs_inode_reload_unlinked_bucket(
>  	struct xfs_buf		*agibp;
>  	struct xfs_agi		*agi;
>  	struct xfs_perag	*pag;
> +	struct xarray		seen_aginos;
>  	xfs_agnumber_t		agno = XFS_INO_TO_AGNO(mp, ip->i_ino);
>  	xfs_agino_t		agino = XFS_INO_TO_AGINO(mp, ip->i_ino);
>  	xfs_agino_t		prev_agino, next_agino;
> @@ -2873,6 +2875,8 @@ xfs_inode_reload_unlinked_bucket(
>  	if (error)
>  		return error;
>  
> +	xa_init(&seen_aginos);

Why not use xbitmap.h for this?  Is it too memory-inefficient over an
xarray?

> +
>  	/*
>  	 * We've taken ILOCK_SHARED and the AGI buffer lock to stabilize the
>  	 * incore unlinked list pointers for this inode.  Check once more to
> @@ -2897,6 +2901,30 @@ xfs_inode_reload_unlinked_bucket(
>  	while (next_agino != NULLAGINO) {
>  		struct xfs_inode	*next_ip = NULL;
>  
> +		/*
> +		 * The on-disk unlinked list is corrupt if it points outside this
> +		 * AG, into another bucket, or back to an inode that we already
> +		 * saw during this reload walk.
> +		 */
> +		if (!xfs_verify_agino(pag, next_agino) ||
> +		    next_agino % XFS_AGI_UNLINKED_BUCKETS != bucket) {

Does online fsck need any corrections w.r.t. these new findings?

--D

> +			xfs_buf_mark_corrupt(agibp);
> +			xfs_ag_mark_sick(pag, XFS_SICK_AG_AGI);
> +			error = -EFSCORRUPTED;
> +			break;
> +		}
> +
> +		if (xa_load(&seen_aginos, next_agino)) {
> +			xfs_buf_mark_corrupt(agibp);
> +			xfs_ag_mark_sick(pag, XFS_SICK_AG_AGI);
> +			error = -EFSCORRUPTED;
> +			break;
> +		}
> +		error = xa_err(xa_store(&seen_aginos, next_agino,
> +					xa_mk_value(1), GFP_NOFS));
> +		if (error)
> +			break;
> +
>  		/* Found this caller's inode, set its backlink. */
>  		if (next_agino == agino) {
>  			next_ip = ip;
> @@ -2931,6 +2959,7 @@ xfs_inode_reload_unlinked_bucket(
>  	}
>  
>  out_agibp:
> +	xa_destroy(&seen_aginos);
>  	xfs_trans_brelse(tp, agibp);
>  	/* Should have found this inode somewhere in the iunlinked bucket. */
>  	if (!error && !foundit)
> diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c
> index 09e6678ca4878..8ca70bbbfac81 100644
> --- a/fs/xfs/xfs_log_recover.c
> +++ b/fs/xfs/xfs_log_recover.c
> @@ -3,6 +3,8 @@
>   * Copyright (c) 2000-2006 Silicon Graphics, Inc.
>   * All Rights Reserved.
>   */
> +#include <linux/xarray.h>
> +
>  #include "xfs_platform.h"
>  #include "xfs_fs.h"
>  #include "xfs_shared.h"
> @@ -28,6 +30,7 @@
>  #include "xfs_ag.h"
>  #include "xfs_quota.h"
>  #include "xfs_reflink.h"
> +#include "xfs_health.h"
>  
>  #define BLK_AVG(blk1, blk2)	((blk1+blk2) >> 1)
>  
> @@ -2726,11 +2729,31 @@ xlog_recover_iunlink_bucket(
>  	struct xfs_mount	*mp = pag_mount(pag);
>  	struct xfs_inode	*prev_ip = NULL;
>  	struct xfs_inode	*ip;
> +	struct xarray		seen_aginos;
>  	xfs_agino_t		prev_agino, agino;
>  	int			error = 0;
>  
> +	xa_init(&seen_aginos);
> +
>  	agino = be32_to_cpu(agi->agi_unlinked[bucket]);
>  	while (agino != NULLAGINO) {
> +		if (!xfs_verify_agino(pag, agino) ||
> +		    agino % XFS_AGI_UNLINKED_BUCKETS != bucket) {
> +			xfs_ag_mark_sick(pag, XFS_SICK_AG_AGI);
> +			error = -EFSCORRUPTED;
> +			break;
> +		}
> +
> +		if (xa_load(&seen_aginos, agino)) {
> +			xfs_ag_mark_sick(pag, XFS_SICK_AG_AGI);
> +			error = -EFSCORRUPTED;
> +			break;
> +		}
> +		error = xa_err(xa_store(&seen_aginos, agino, xa_mk_value(1),
> +					GFP_NOFS));
> +		if (error)
> +			break;
> +
>  		error = xfs_iget(mp, NULL, xfs_agino_to_ino(pag, agino), 0, 0,
>  				&ip);
>  		if (error)
> @@ -2771,8 +2794,9 @@ xlog_recover_iunlink_bucket(
>  
>  		error2 = xfs_inodegc_flush(mp);
>  		if (error2 && !error)
> -			return error2;
> +			error = error2;
>  	}
> +	xa_destroy(&seen_aginos);
>  	return error;
>  }
>  
> -- 
> 2.53.0
> 

  reply	other threads:[~2026-05-19  5:02 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-18  1:31 [PATCH] xfs: detect cycles in recovered unlinked inode lists Michael Bommarito
2026-05-19  5:02 ` Darrick J. Wong [this message]
2026-05-19  8:30   ` Christoph Hellwig
2026-05-19 12:48     ` Michael Bommarito
2026-05-20 12:19       ` Carlos Maiolino
2026-05-20 12:46         ` Michael Bommarito

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260519050235.GI9568@frogsfrogsfrogs \
    --to=djwong@kernel.org \
    --cc=cem@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-xfs@vger.kernel.org \
    --cc=michael.bommarito@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox