xfs: make xchk_iget safer in the presence of corrupt inode btrees
authorDarrick J. Wong <djwong@kernel.org>
Thu, 7 Dec 2023 02:40:54 +0000 (18:40 -0800)
committerDarrick J. Wong <djwong@kernel.org>
Thu, 7 Dec 2023 02:45:17 +0000 (18:45 -0800)
When scrub is trying to iget an inode, ensure that it won't end up
deadlocked on a cycle in the inode btree by using an empty transaction
to store all the buffers.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
fs/xfs/scrub/common.c
fs/xfs/scrub/common.h
fs/xfs/scrub/inode.c

index de24532fe08309d7d57555e5b8ba6ecdd38aabe5..23944fcc1a6cabfe4438cb06b9037525244f0067 100644 (file)
@@ -733,6 +733,8 @@ xchk_iget(
        xfs_ino_t               inum,
        struct xfs_inode        **ipp)
 {
+       ASSERT(sc->tp != NULL);
+
        return xfs_iget(sc->mp, sc->tp, inum, XFS_IGET_UNTRUSTED, 0, ipp);
 }
 
@@ -882,8 +884,8 @@ xchk_iget_for_scrubbing(
        if (!xfs_verify_ino(sc->mp, sc->sm->sm_ino))
                return -ENOENT;
 
-       /* Try a regular untrusted iget. */
-       error = xchk_iget(sc, sc->sm->sm_ino, &ip);
+       /* Try a safe untrusted iget. */
+       error = xchk_iget_safe(sc, sc->sm->sm_ino, &ip);
        if (!error)
                return xchk_install_handle_inode(sc, ip);
        if (error == -ENOENT)
index cabdc0e16838c770ac48a1e518b4159ef996636b..c83cf9e5b55f0f7904df7367ef1dda127ff0b3c1 100644 (file)
@@ -151,12 +151,37 @@ void xchk_iunlock(struct xfs_scrub *sc, unsigned int ilock_flags);
 
 void xchk_buffer_recheck(struct xfs_scrub *sc, struct xfs_buf *bp);
 
+/*
+ * Grab the inode at @inum.  The caller must have created a scrub transaction
+ * so that we can confirm the inumber by walking the inobt and not deadlock on
+ * a loop in the inobt.
+ */
 int xchk_iget(struct xfs_scrub *sc, xfs_ino_t inum, struct xfs_inode **ipp);
 int xchk_iget_agi(struct xfs_scrub *sc, xfs_ino_t inum,
                struct xfs_buf **agi_bpp, struct xfs_inode **ipp);
 void xchk_irele(struct xfs_scrub *sc, struct xfs_inode *ip);
 int xchk_install_handle_inode(struct xfs_scrub *sc, struct xfs_inode *ip);
 
+/*
+ * Safe version of (untrusted) xchk_iget that uses an empty transaction to
+ * avoid deadlocking on loops in the inobt.  This should only be used in a
+ * scrub or repair setup routine, and only prior to grabbing a transaction.
+ */
+static inline int
+xchk_iget_safe(struct xfs_scrub *sc, xfs_ino_t inum, struct xfs_inode **ipp)
+{
+       int     error;
+
+       ASSERT(sc->tp == NULL);
+
+       error = xchk_trans_alloc(sc, 0);
+       if (error)
+               return error;
+       error = xchk_iget(sc, inum, ipp);
+       xchk_trans_cancel(sc);
+       return error;
+}
+
 /*
  * Don't bother cross-referencing if we already found corruption or cross
  * referencing discrepancies.
index 889f556bc98f60cde87832cd0d1612cf49d7c919..b7a93380a1ab08942c075ea18e3c9fcb2bd7b182 100644 (file)
@@ -95,8 +95,8 @@ xchk_setup_inode(
        if (!xfs_verify_ino(sc->mp, sc->sm->sm_ino))
                return -ENOENT;
 
-       /* Try a regular untrusted iget. */
-       error = xchk_iget(sc, sc->sm->sm_ino, &ip);
+       /* Try a safe untrusted iget. */
+       error = xchk_iget_safe(sc, sc->sm->sm_ino, &ip);
        if (!error)
                return xchk_install_handle_iscrub(sc, ip);
        if (error == -ENOENT)