Skip to content

Commit 4eb559d

Browse files
author
Darrick J. Wong
committed
Merge tag 'refcount-cow-domain-6.1_2022-10-31' of git://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux into xfs-6.1-fixesA
xfs: improve runtime refcountbt corruption detection Fuzz testing of the refcount btree demonstrated a weakness in validation of refcount btree records during normal runtime. The idea of using the upper bit of the rc_startblock field to separate the refcount records into one group for shared space and another for CoW staging extents was added at the last minute. The incore struct left this bit encoded in the upper bit of the startblock field, which makes it all too easy for arithmetic operations to overflow if we don't detect the cowflag properly. When I ran a norepair fuzz tester, I was able to crash the kernel on one of these accidental overflows by fuzzing a key record in a node block, which broke lookups. To fix the problem, make the domain (shared/cow) a separate field in the incore record. Unfortunately, a customer also hit this once in production. Due to bugs in the kernel running on the VM host, writes to the disk image would occasionally be lost. Given sufficient memory pressure on the VM guest, a refcountbt xfs_buf could be reclaimed and later reloaded from the stale copy on the virtual disk. The stale disk contents were a refcount btree leaf block full of records for the wrong domain, and this caused an infinite loop in the guest VM. v2: actually include the refcount adjust loop invariant checking patch; move the deferred refcount continuation checks earlier in the series; break up the megapatch into smaller pieces; fix an uninitialized list error. v3: in the continuation check patch, verify the per-ag extent before converting it to a fsblock Signed-off-by: Darrick J. Wong <[email protected]> * tag 'refcount-cow-domain-6.1_2022-10-31' of git://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux: xfs: rename XFS_REFC_COW_START to _COWFLAG xfs: fix uninitialized list head in struct xfs_refcount_recovery xfs: fix agblocks check in the cow leftover recovery function xfs: check record domain when accessing refcount records xfs: remove XFS_FIND_RCEXT_SHARED and _COW xfs: refactor domain and refcount checking xfs: report refcount domain in tracepoints xfs: track cow/shared record domains explicitly in xfs_refcount_irec xfs: refactor refcount record usage in xchk_refcountbt_rec xfs: move _irec structs to xfs_types.h xfs: check deferred refcount op continuation parameters xfs: create a predicate to verify per-AG extents xfs: make sure aglen never goes negative in xfs_refcount_adjust_extents
2 parents 9f187ba + 8b97215 commit 4eb559d

File tree

12 files changed

+368
-184
lines changed

12 files changed

+368
-184
lines changed

fs/xfs/libxfs/xfs_ag.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,21 @@ xfs_verify_agbno(struct xfs_perag *pag, xfs_agblock_t agbno)
133133
return true;
134134
}
135135

136+
static inline bool
137+
xfs_verify_agbext(
138+
struct xfs_perag *pag,
139+
xfs_agblock_t agbno,
140+
xfs_agblock_t len)
141+
{
142+
if (agbno + len <= agbno)
143+
return false;
144+
145+
if (!xfs_verify_agbno(pag, agbno))
146+
return false;
147+
148+
return xfs_verify_agbno(pag, agbno + len - 1);
149+
}
150+
136151
/*
137152
* Verify that an AG inode number pointer neither points outside the AG
138153
* nor points at static metadata.

fs/xfs/libxfs/xfs_alloc.c

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,7 @@ xfs_alloc_get_rec(
263263
goto out_bad_rec;
264264

265265
/* check for valid extent range, including overflow */
266-
if (!xfs_verify_agbno(pag, *bno))
267-
goto out_bad_rec;
268-
if (*bno > *bno + *len)
269-
goto out_bad_rec;
270-
if (!xfs_verify_agbno(pag, *bno + *len - 1))
266+
if (!xfs_verify_agbext(pag, *bno, *len))
271267
goto out_bad_rec;
272268

273269
return 0;

fs/xfs/libxfs/xfs_format.h

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,20 +1564,6 @@ struct xfs_rmap_rec {
15641564
#define RMAPBT_UNUSED_OFFSET_BITLEN 7
15651565
#define RMAPBT_OFFSET_BITLEN 54
15661566

1567-
#define XFS_RMAP_ATTR_FORK (1 << 0)
1568-
#define XFS_RMAP_BMBT_BLOCK (1 << 1)
1569-
#define XFS_RMAP_UNWRITTEN (1 << 2)
1570-
#define XFS_RMAP_KEY_FLAGS (XFS_RMAP_ATTR_FORK | \
1571-
XFS_RMAP_BMBT_BLOCK)
1572-
#define XFS_RMAP_REC_FLAGS (XFS_RMAP_UNWRITTEN)
1573-
struct xfs_rmap_irec {
1574-
xfs_agblock_t rm_startblock; /* extent start block */
1575-
xfs_extlen_t rm_blockcount; /* extent length */
1576-
uint64_t rm_owner; /* extent owner */
1577-
uint64_t rm_offset; /* offset within the owner */
1578-
unsigned int rm_flags; /* state flags */
1579-
};
1580-
15811567
/*
15821568
* Key structure
15831569
*
@@ -1626,7 +1612,7 @@ unsigned int xfs_refc_block(struct xfs_mount *mp);
16261612
* on the startblock. This speeds up mount time deletion of stale
16271613
* staging extents because they're all at the right side of the tree.
16281614
*/
1629-
#define XFS_REFC_COW_START ((xfs_agblock_t)(1U << 31))
1615+
#define XFS_REFC_COWFLAG (1U << 31)
16301616
#define REFCNTBT_COWFLAG_BITLEN 1
16311617
#define REFCNTBT_AGBLOCK_BITLEN 31
16321618

@@ -1640,12 +1626,6 @@ struct xfs_refcount_key {
16401626
__be32 rc_startblock; /* starting block number */
16411627
};
16421628

1643-
struct xfs_refcount_irec {
1644-
xfs_agblock_t rc_startblock; /* starting block number */
1645-
xfs_extlen_t rc_blockcount; /* count of free blocks */
1646-
xfs_nlink_t rc_refcount; /* number of inodes linked here */
1647-
};
1648-
16491629
#define MAXREFCOUNT ((xfs_nlink_t)~0U)
16501630
#define MAXREFCEXTLEN ((xfs_extlen_t)~0U)
16511631

0 commit comments

Comments
 (0)