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
>
next prev parent 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.