Skip to content

Commit 7ac14fa

Browse files
author
Darrick J. Wong
committed
xfs: ensure that all metadata and data blocks are not cow staging extents
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>
1 parent 7ad9ea6 commit 7ac14fa

7 files changed

Lines changed: 39 additions & 4 deletions

File tree

fs/xfs/scrub/agheader.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ xchk_superblock_xref(
5353
xchk_xref_is_not_inode_chunk(sc, agbno, 1);
5454
xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
5555
xchk_xref_is_not_shared(sc, agbno, 1);
56+
xchk_xref_is_not_cow_staging(sc, agbno, 1);
5657

5758
/* scrub teardown will take care of sc->sa for us */
5859
}
@@ -517,6 +518,7 @@ xchk_agf_xref(
517518
xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
518519
xchk_agf_xref_btreeblks(sc);
519520
xchk_xref_is_not_shared(sc, agbno, 1);
521+
xchk_xref_is_not_cow_staging(sc, agbno, 1);
520522
xchk_agf_xref_refcblks(sc);
521523

522524
/* scrub teardown will take care of sc->sa for us */
@@ -644,6 +646,7 @@ xchk_agfl_block_xref(
644646
xchk_xref_is_not_inode_chunk(sc, agbno, 1);
645647
xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_AG);
646648
xchk_xref_is_not_shared(sc, agbno, 1);
649+
xchk_xref_is_not_cow_staging(sc, agbno, 1);
647650
}
648651

649652
/* Scrub an AGFL block. */
@@ -700,6 +703,7 @@ xchk_agfl_xref(
700703
xchk_xref_is_not_inode_chunk(sc, agbno, 1);
701704
xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
702705
xchk_xref_is_not_shared(sc, agbno, 1);
706+
xchk_xref_is_not_cow_staging(sc, agbno, 1);
703707

704708
/*
705709
* Scrub teardown will take care of sc->sa for us. Leave sc->sa
@@ -855,6 +859,7 @@ xchk_agi_xref(
855859
xchk_agi_xref_icounts(sc);
856860
xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
857861
xchk_xref_is_not_shared(sc, agbno, 1);
862+
xchk_xref_is_not_cow_staging(sc, agbno, 1);
858863
xchk_agi_xref_fiblocks(sc);
859864

860865
/* scrub teardown will take care of sc->sa for us */

fs/xfs/scrub/alloc.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ xchk_allocbt_xref(
9090
xchk_xref_is_not_inode_chunk(sc, agbno, len);
9191
xchk_xref_has_no_owner(sc, agbno, len);
9292
xchk_xref_is_not_shared(sc, agbno, len);
93+
xchk_xref_is_not_cow_staging(sc, agbno, len);
9394
}
9495

9596
/* Scrub a bnobt/cntbt record. */

fs/xfs/scrub/bmap.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,12 +328,17 @@ xchk_bmap_iextent_xref(
328328
xchk_bmap_xref_rmap(info, irec, agbno);
329329
switch (info->whichfork) {
330330
case XFS_DATA_FORK:
331-
if (xfs_is_reflink_inode(info->sc->ip))
332-
break;
333-
fallthrough;
331+
if (!xfs_is_reflink_inode(info->sc->ip))
332+
xchk_xref_is_not_shared(info->sc, agbno,
333+
irec->br_blockcount);
334+
xchk_xref_is_not_cow_staging(info->sc, agbno,
335+
irec->br_blockcount);
336+
break;
334337
case XFS_ATTR_FORK:
335338
xchk_xref_is_not_shared(info->sc, agbno,
336339
irec->br_blockcount);
340+
xchk_xref_is_not_cow_staging(info->sc, agbno,
341+
irec->br_blockcount);
337342
break;
338343
case XFS_COW_FORK:
339344
xchk_xref_is_cow_staging(info->sc, agbno,

fs/xfs/scrub/ialloc.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ xchk_iallocbt_chunk(
115115
xchk_btree_set_corrupt(bs->sc, bs->cur, 0);
116116

117117
xchk_iallocbt_chunk_xref(bs->sc, irec, agino, bno, len);
118-
118+
xchk_xref_is_not_cow_staging(bs->sc, bno, len);
119119
return true;
120120
}
121121

fs/xfs/scrub/inode.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ xchk_inode_xref(
558558
xchk_inode_xref_finobt(sc, ino);
559559
xchk_xref_is_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_INODES);
560560
xchk_xref_is_not_shared(sc, agbno, 1);
561+
xchk_xref_is_not_cow_staging(sc, agbno, 1);
561562
xchk_inode_xref_bmap(sc, dip);
562563

563564
out_free:

fs/xfs/scrub/refcount.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,3 +555,24 @@ xchk_xref_is_not_shared(
555555
if (outcome != XBTREE_RECPACKING_EMPTY)
556556
xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0);
557557
}
558+
559+
/* xref check that the extent is not being used for CoW staging. */
560+
void
561+
xchk_xref_is_not_cow_staging(
562+
struct xfs_scrub *sc,
563+
xfs_agblock_t agbno,
564+
xfs_extlen_t len)
565+
{
566+
enum xbtree_recpacking outcome;
567+
int error;
568+
569+
if (!sc->sa.refc_cur || xchk_skip_xref(sc->sm))
570+
return;
571+
572+
error = xfs_refcount_has_records(sc->sa.refc_cur, XFS_REFC_DOMAIN_COW,
573+
agbno, len, &outcome);
574+
if (!xchk_should_check_xref(sc, &error, &sc->sa.refc_cur))
575+
return;
576+
if (outcome != XBTREE_RECPACKING_EMPTY)
577+
xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0);
578+
}

fs/xfs/scrub/scrub.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ void xchk_xref_is_cow_staging(struct xfs_scrub *sc, xfs_agblock_t bno,
172172
xfs_extlen_t len);
173173
void xchk_xref_is_not_shared(struct xfs_scrub *sc, xfs_agblock_t bno,
174174
xfs_extlen_t len);
175+
void xchk_xref_is_not_cow_staging(struct xfs_scrub *sc, xfs_agblock_t bno,
176+
xfs_extlen_t len);
175177
#ifdef CONFIG_XFS_RT
176178
void xchk_xref_is_used_rt_space(struct xfs_scrub *sc, xfs_rtblock_t rtbno,
177179
xfs_extlen_t len);

0 commit comments

Comments
 (0)