xfs: ensure that all metadata and data blocks are not cow staging extents
authorDarrick J. Wong <djwong@kernel.org>
Wed, 12 Apr 2023 02:00:12 +0000 (19:00 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Wed, 12 Apr 2023 02:00:12 +0000 (19:00 -0700)
Make sure that all filesystem metadata blocks and file data blocks are
not also marked as CoW staging extents.  The extra checking added here
was inspired by an actual VM host filesystem corruption incident due to
bugs in the CoW handling of 4.x kernels.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
fs/xfs/scrub/agheader.c
fs/xfs/scrub/alloc.c
fs/xfs/scrub/bmap.c
fs/xfs/scrub/ialloc.c
fs/xfs/scrub/inode.c
fs/xfs/scrub/refcount.c
fs/xfs/scrub/scrub.h

index 87cb13a6e84a068e0887bc0161553933d721ccd2..1a84153afa91711013a4462a570af5910b283ead 100644 (file)
@@ -53,6 +53,7 @@ xchk_superblock_xref(
        xchk_xref_is_not_inode_chunk(sc, agbno, 1);
        xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
        xchk_xref_is_not_shared(sc, agbno, 1);
+       xchk_xref_is_not_cow_staging(sc, agbno, 1);
 
        /* scrub teardown will take care of sc->sa for us */
 }
@@ -517,6 +518,7 @@ xchk_agf_xref(
        xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
        xchk_agf_xref_btreeblks(sc);
        xchk_xref_is_not_shared(sc, agbno, 1);
+       xchk_xref_is_not_cow_staging(sc, agbno, 1);
        xchk_agf_xref_refcblks(sc);
 
        /* scrub teardown will take care of sc->sa for us */
@@ -644,6 +646,7 @@ xchk_agfl_block_xref(
        xchk_xref_is_not_inode_chunk(sc, agbno, 1);
        xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_AG);
        xchk_xref_is_not_shared(sc, agbno, 1);
+       xchk_xref_is_not_cow_staging(sc, agbno, 1);
 }
 
 /* Scrub an AGFL block. */
@@ -700,6 +703,7 @@ xchk_agfl_xref(
        xchk_xref_is_not_inode_chunk(sc, agbno, 1);
        xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
        xchk_xref_is_not_shared(sc, agbno, 1);
+       xchk_xref_is_not_cow_staging(sc, agbno, 1);
 
        /*
         * Scrub teardown will take care of sc->sa for us.  Leave sc->sa
@@ -855,6 +859,7 @@ xchk_agi_xref(
        xchk_agi_xref_icounts(sc);
        xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
        xchk_xref_is_not_shared(sc, agbno, 1);
+       xchk_xref_is_not_cow_staging(sc, agbno, 1);
        xchk_agi_xref_fiblocks(sc);
 
        /* scrub teardown will take care of sc->sa for us */
index 5920fe051543ea52059f460801432683ba29906c..12dd55ac2a4f1e78b38c476058af61810d99eacc 100644 (file)
@@ -90,6 +90,7 @@ xchk_allocbt_xref(
        xchk_xref_is_not_inode_chunk(sc, agbno, len);
        xchk_xref_has_no_owner(sc, agbno, len);
        xchk_xref_is_not_shared(sc, agbno, len);
+       xchk_xref_is_not_cow_staging(sc, agbno, len);
 }
 
 /* Scrub a bnobt/cntbt record. */
index 6188eba672e54fd218d25b2af88faf7343688429..be2c4da2808ba3ffe73e2a9dfc0ec6ae539d5b81 100644 (file)
@@ -328,12 +328,17 @@ xchk_bmap_iextent_xref(
        xchk_bmap_xref_rmap(info, irec, agbno);
        switch (info->whichfork) {
        case XFS_DATA_FORK:
-               if (xfs_is_reflink_inode(info->sc->ip))
-                       break;
-               fallthrough;
+               if (!xfs_is_reflink_inode(info->sc->ip))
+                       xchk_xref_is_not_shared(info->sc, agbno,
+                                       irec->br_blockcount);
+               xchk_xref_is_not_cow_staging(info->sc, agbno,
+                               irec->br_blockcount);
+               break;
        case XFS_ATTR_FORK:
                xchk_xref_is_not_shared(info->sc, agbno,
                                irec->br_blockcount);
+               xchk_xref_is_not_cow_staging(info->sc, agbno,
+                               irec->br_blockcount);
                break;
        case XFS_COW_FORK:
                xchk_xref_is_cow_staging(info->sc, agbno,
index ca5a7e0f5451c018a8e545a5d7b5ff3545c3aac8..6d08613db32f08bbb0cb13d3ec22def0d03972d0 100644 (file)
@@ -115,7 +115,7 @@ xchk_iallocbt_chunk(
                xchk_btree_set_corrupt(bs->sc, bs->cur, 0);
 
        xchk_iallocbt_chunk_xref(bs->sc, irec, agino, bno, len);
-
+       xchk_xref_is_not_cow_staging(bs->sc, bno, len);
        return true;
 }
 
index bbf9432c02c2c83dbd3f4f2f2d897ab41569b998..50ebd72f6d959a49e00fa540c2f18e776d0bbe3e 100644 (file)
@@ -558,6 +558,7 @@ xchk_inode_xref(
        xchk_inode_xref_finobt(sc, ino);
        xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_INODES);
        xchk_xref_is_not_shared(sc, agbno, 1);
+       xchk_xref_is_not_cow_staging(sc, agbno, 1);
        xchk_inode_xref_bmap(sc, dip);
 
 out_free:
index 771a591a4aeba274d5c1f23b3d98adc60db92050..db9e46a4f8d4371dd08e1fe1c7d578afe7f85055 100644 (file)
@@ -555,3 +555,24 @@ xchk_xref_is_not_shared(
        if (outcome != XBTREE_RECPACKING_EMPTY)
                xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0);
 }
+
+/* xref check that the extent is not being used for CoW staging. */
+void
+xchk_xref_is_not_cow_staging(
+       struct xfs_scrub        *sc,
+       xfs_agblock_t           agbno,
+       xfs_extlen_t            len)
+{
+       enum xbtree_recpacking  outcome;
+       int                     error;
+
+       if (!sc->sa.refc_cur || xchk_skip_xref(sc->sm))
+               return;
+
+       error = xfs_refcount_has_records(sc->sa.refc_cur, XFS_REFC_DOMAIN_COW,
+                       agbno, len, &outcome);
+       if (!xchk_should_check_xref(sc, &error, &sc->sa.refc_cur))
+               return;
+       if (outcome != XBTREE_RECPACKING_EMPTY)
+               xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0);
+}
index d85c3b883b4cedb95f0c951d80a9d8b34d7612fb..b6f452eb9645ed6584de4f958b9ec388041f9132 100644 (file)
@@ -172,6 +172,8 @@ void xchk_xref_is_cow_staging(struct xfs_scrub *sc, xfs_agblock_t bno,
                xfs_extlen_t len);
 void xchk_xref_is_not_shared(struct xfs_scrub *sc, xfs_agblock_t bno,
                xfs_extlen_t len);
+void xchk_xref_is_not_cow_staging(struct xfs_scrub *sc, xfs_agblock_t bno,
+               xfs_extlen_t len);
 #ifdef CONFIG_XFS_RT
 void xchk_xref_is_used_rt_space(struct xfs_scrub *sc, xfs_rtblock_t rtbno,
                xfs_extlen_t len);