FAILED: patch "[PATCH] xfs: close crash window in attr dabtree inactivation" failed to apply to 6.18-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.18-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.18.y
git checkout FETCH_HEAD
git cherry-pick -x b854e1c4eff3473b6d3a9ae74129ac5c48bc0b61
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026033024-poach-sequester-14e1@gregkh' --subject-prefix 'PATCH 6.18.y' HEAD^..
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From b854e1c4eff3473b6d3a9ae74129ac5c48bc0b61 Mon Sep 17 00:00:00 2001
From: Long Li <[email protected]>
Date: Tue, 17 Mar 2026 09:51:55 +0800
Subject: [PATCH] xfs: close crash window in attr dabtree inactivation
When inactivating an inode with node-format extended attributes, xfs_attr3_node_inactive() invalidates all child leaf/node blocks via xfs_trans_binval(), but intentionally does not remove the corresponding entries from their parent node blocks. The implicit assumption is that xfs_attr_inactive() will truncate the entire attr fork to zero extents afterwards, so log recovery will never reach the root node and follow those stale pointers.
However, if a log shutdown occurs after the leaf/node block cancellations commit but before the attr bmap truncation commits, this assumption breaks. Recovery replays the attr bmap intact (the inode still has attr fork extents), but suppresses replay of all cancelled leaf/node blocks, maybe leaving them as stale data on disk. On the next mount, xlog_recover_process_iunlinks() retries inactivation and attempts to read the root node via the attr bmap. If the root node was not replayed, reading the unreplayed root block triggers a metadata verification failure immediately; if it was replayed, following its child pointers to unreplayed child blocks triggers the same failure:
XFS (pmem0): Metadata corruption detected at
xfs_da3_node_read_verify+0x53/0x220, xfs_da3_node block 0x78 XFS (pmem0): Unmount and run xfs_repair XFS (pmem0): First 128 bytes of corrupted metadata buffer: 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ XFS (pmem0): metadata I/O error in "xfs_da_read_buf+0x104/0x190" at daddr 0x78 len 8 error 117
Fix this in two places:
In xfs_attr3_node_inactive(), after calling xfs_trans_binval() on a child block, immediately remove the entry that references it from the parent node in the same transaction. This eliminates the window where the parent holds a pointer to a cancelled block. Once all children are removed, the now-empty root node is converted to a leaf block within the same transaction. This node-to-leaf conversion is necessary for crash safety. If the system shutdown after the empty node is written to the log but before the second-phase bmap truncation commits, log recovery will attempt to verify the root block on disk. xfs_da3_node_verify() does not permit a node block with count == 0; such a block will fail verification and trigger a metadata corruption shutdown. on the other hand, leaf blocks are allowed to have this transient state.
In xfs_attr_inactive(), split the attr fork truncation into two explicit phases. First, truncate all extents beyond the root block (the child extents whose parent references have already been removed above). Second, invalidate the root block and truncate the attr bmap to zero in a single transaction. The two operations in the second phase must be
atomic: as long as the attr bmap has any non-zero length, recovery can follow it to the root block, so the root block invalidation must commit together with the bmap-to-zero truncation.
fs/xfs/xfs_attr_inactive.c +59 -40
@@ -140,7 +140,7 @@ xfs_attr3_node_inactive(xfs_daddr_t parent_blkno, child_blkno;struct xfs_buf *child_bp;struct xfs_da3_icnode_hdr ichdr; -int error, i; +int error;/* * Since this code is recursive (gasp!) we must protect ourselves.
@@ -152,7 +152,7 @@ xfs_attr3_node_inactive(return -EFSCORRUPTED ; } -xfs_da3_node_hdr_from_disk (dp->i_mount, &ichdr, bp->b_addr); +xfs_da3_node_hdr_from_disk (mp, &ichdr, bp->b_addr); parent_blkno =xfs_buf_daddr (bp);if (!ichdr.count) {xfs_trans_brelse (*trans, bp);
@@ -167,7 +167,7 @@ xfs_attr3_node_inactive(* over the leaves removing all of them. If this is higher up * in the tree, recurse downward. */ -for (i =0 ; i < ichdr.count; i++) { +while (ichdr.count >0 ) {/* * Read the subsidiary block to see what we have to work with. * Don't do this in a transaction. This is a depth-first
@@ -218,29 +218,32 @@ xfs_attr3_node_inactive(xfs_trans_binval (*trans, child_bp); child_bp =NULL ; -/* -* If we're not done, re-read the parent to get the next -* child block number. -*/ -if (i +1 < ichdr.count) { -struct xfs_da3_icnode_hdr phdr; + error =xfs_da3_node_read_mapped (*trans, dp, + parent_blkno, &bp,XFS_ATTR_FORK ); +if (error) +return error; - error =xfs_da3_node_read_mapped (*trans, dp, - parent_blkno, &bp,XFS_ATTR_FORK ); +/* +* Remove entry from parent node, prevents being indexed to. +*/ +xfs_attr3_node_entry_remove (*trans, dp, bp,0 ); + +xfs_da3_node_hdr_from_disk (mp, &ichdr, bp->b_addr); + bp =NULL ; + +if (ichdr.count >0 ) { +/* +* If we're not done, get the next child block number. +*/ + child_fsb =be32_to_cpu (ichdr.btree[0 ].before); + +/* +* Atomically commit the whole invalidate stuff. +*/ + error =xfs_trans_roll_inode (trans, dp);if (error)return error; -xfs_da3_node_hdr_from_disk (dp->i_mount, &phdr, - bp->b_addr); - child_fsb =be32_to_cpu (phdr.btree[i +1 ].before); -xfs_trans_brelse (*trans, bp); - bp =NULL ; } -/* -* Atomically commit the whole invalidate stuff. -*/ - error =xfs_trans_roll_inode (trans, dp); -if (error) -return error; }return 0 ;
@@ -257,10 +260,8 @@ xfs_attr3_root_inactive(struct xfs_trans **trans, struct xfs_inode *dp) { - struct xfs_mount *mp = dp->i_mount; struct xfs_da_blkinfo *info;struct xfs_buf *bp; -xfs_daddr_t blkno;int error;/*
@@ -272,7 +273,6 @@ xfs_attr3_root_inactive( error =xfs_da3_node_read (*trans, dp,0 , &bp,XFS_ATTR_FORK );if (error)return error; - blkno =xfs_buf_daddr (bp);/* * Invalidate the tree, even if the "tree" is only a single leaf block.
@@ -283,10 +283,26 @@ xfs_attr3_root_inactive(case cpu_to_be16 (XFS_DA_NODE_MAGIC ):case cpu_to_be16 (XFS_DA3_NODE_MAGIC ): error =xfs_attr3_node_inactive (trans, dp, bp,1 ); +/* +* Empty root node block are not allowed, convert it to leaf. +*/ +if (!error) + error =xfs_attr3_leaf_init (*trans, dp,0 ); +if (!error) + error =xfs_trans_roll_inode (trans, dp);break ;case cpu_to_be16 (XFS_ATTR_LEAF_MAGIC ):case cpu_to_be16 (XFS_ATTR3_LEAF_MAGIC ): error =xfs_attr3_leaf_inactive (trans, dp, bp); +/* +* Reinit the leaf before truncating extents so that a crash +* mid-truncation leaves an empty leaf rather than one with +* entries that may reference freed remote value blocks. +*/ +if (!error) + error =xfs_attr3_leaf_init (*trans, dp,0 ); +if (!error) + error =xfs_trans_roll_inode (trans, dp);break ;default :xfs_dirattr_mark_sick (dp,XFS_ATTR_FORK );
@@ -295,21 +311,6 @@ xfs_attr3_root_inactive(xfs_trans_brelse (*trans, bp);break ; } -if (error) -return error; - -/* -* Invalidate the incore copy of the root block. -*/ - error =xfs_trans_get_buf (*trans, mp->m_ddev_targp, blkno, -XFS_FSB_TO_BB (mp, mp->m_attr_geo->fsbcount),0 , &bp); -if (error) -return error; -xfs_trans_binval (*trans, bp);/* remove from cache */ -/* -* Commit the invalidate and start the next transaction. -*/ - error =xfs_trans_roll_inode (trans, dp);return error; }
@@ -328,6 +329,7 @@ xfs_attr_inactive( {struct xfs_trans *trans;struct xfs_mount *mp; +struct xfs_buf *bp;int lock_mode =XFS_ILOCK_SHARED ;int error =0 ;
@@ -363,10 +365,27 @@ xfs_attr_inactive(* removal below. */ if (dp->i_af.if_nextents >0 ) { +/* +* Invalidate and truncate all blocks but leave the root block. +*/ error =xfs_attr3_root_inactive (&trans, dp);if (error) goto out_cancel; + error =xfs_itruncate_extents (&trans, dp,XFS_ATTR_FORK , +XFS_FSB_TO_B (mp, mp->m_attr_geo->fsbcount)); +if (error) + goto out_cancel; + +/* +* Invalidate and truncate the root block and ensure that the +* operation is completed within a single transaction. +*/ + error =xfs_da_get_buf (trans, dp,0 , &bp,XFS_ATTR_FORK ); +if (error) + goto out_cancel; + +xfs_trans_binval (trans, bp); error =xfs_itruncate_extents (&trans, dp,XFS_ATTR_FORK ,0 );if (error) goto out_cancel;
Sasha Levin
maintainerPATCH 1/3 xfs: factor out xfs_attr3_node_entry_remove
From: Long Li <[email protected]>
[ Upstream commit ce4e789cf3561c9fac73cc24445bfed9ea0c514b ]
Factor out wrapper xfs_attr3_node_entry_remove function, which exported for external use.
Stable-dep-of: b854e1c4eff3 ("xfs: close crash window in attr dabtree inactivation")
--- fs/xfs/libxfs/xfs_da_btree.c | 53 ++++++++++++++++++++++++++++-------- fs/xfs/libxfs/xfs_da_btree.h | 2 ++ 2 files changed, 44 insertions(+), 11 deletions(-)
fs/xfs/libxfs/xfs_da_btree.c +42 -11
@@ -1506,21 +1506,20 @@ xfs_da3_fixhashpath( }/* -* Remove an entry from an intermediate node. +* Internal implementation to remove an entry from an intermediate node. */ STATIC void -xfs_da3_node_remove ( -struct xfs_da_state *state, -struct xfs_da_state_blk *drop_blk) +__xfs_da3_node_remove ( +struct xfs_trans *tp, +struct xfs_inode *dp, +struct xfs_da_geometry *geo, +struct xfs_da_state_blk *drop_blk) {struct xfs_da_intnode *node;struct xfs_da3_icnode_hdr nodehdr;struct xfs_da_node_entry *btree;int index;int tmp; -struct xfs_inode *dp = state->args->dp; - -trace_xfs_da_node_remove (state->args); node = drop_blk->bp->b_addr;xfs_da3_node_hdr_from_disk (dp->i_mount, &nodehdr, node);
@@ -1536,17 +1535,17 @@ xfs_da3_node_remove( tmp = nodehdr.count - index -1 ; tmp *= (uint )sizeof (xfs_da_node_entry_t);memmove (&btree[index], &btree[index +1 ], tmp); -xfs_trans_log_buf (state->args->trans, drop_blk->bp, +xfs_trans_log_buf (tp, drop_blk->bp,XFS_DA_LOGRANGE (node, &btree[index], tmp)); index = nodehdr.count -1 ; }memset (&btree[index],0 ,sizeof (xfs_da_node_entry_t)); -xfs_trans_log_buf (state->args->trans, drop_blk->bp, +xfs_trans_log_buf (tp, drop_blk->bp,XFS_DA_LOGRANGE (node, &btree[index],sizeof (btree[index]))); nodehdr.count -=1 ;xfs_da3_node_hdr_to_disk (dp->i_mount, node, &nodehdr); -xfs_trans_log_buf (state->args->trans, drop_blk->bp, -XFS_DA_LOGRANGE (node, &node->hdr, state->args->geo->node_hdr_size)); +xfs_trans_log_buf (tp, drop_blk->bp, +XFS_DA_LOGRANGE (node, &node->hdr, geo->node_hdr_size));/* * Copy the last hash value from the block to propagate upwards.
@@ -1554,6 +1553,38 @@ xfs_da3_node_remove( drop_blk->hashval =be32_to_cpu (btree[index -1 ].hashval); } +/* +* Remove an entry from an intermediate node. +*/ +STATIC void +xfs_da3_node_remove ( +struct xfs_da_state *state, +struct xfs_da_state_blk *drop_blk) +{ +trace_xfs_da_node_remove (state->args); + +__xfs_da3_node_remove (state->args->trans, state->args->dp, + state->args->geo, drop_blk); +} + +/* +* Remove an entry from an intermediate attr node at the specified index. +*/ +void +xfs_attr3_node_entry_remove ( +struct xfs_trans *tp, +struct xfs_inode *dp, +struct xfs_buf *bp, +int index) +{ +struct xfs_da_state_blk blk = { + .index = index, + .bp = bp, + }; + +__xfs_da3_node_remove (tp, dp, dp->i_mount->m_attr_geo, &blk); +} +/* * Unbalance the elements between two intermediate nodes, * move all Btree elements from one node into another.
fs/xfs/libxfs/xfs_da_btree.h +2 -0
@@ -184,6 +184,8 @@ int xfs_da3_split(xfs_da_state_t *state);int xfs_da3_join (xfs_da_state_t *state);void xfs_da3_fixhashpath (struct xfs_da_state *state,struct xfs_da_state_path *path_to_to_fix); +void xfs_attr3_node_entry_remove (struct xfs_trans *tp,struct xfs_inode *dp, +struct xfs_buf *bp,int index);/* * Routines used for finding things in the Btree.
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/3 xfs: factor out xfs_attr3_leaf_init
From: Long Li <[email protected]>
[ Upstream commit e65bb55d7f8c2041c8fdb73cd29b0b4cad4ed847 ]
Factor out wrapper xfs_attr3_leaf_init function, which exported for external use.
Stable-dep-of: b854e1c4eff3 ("xfs: close crash window in attr dabtree inactivation")
--- fs/xfs/libxfs/xfs_attr_leaf.c | 22 ++++++++++++++++++++++ fs/xfs/libxfs/xfs_attr_leaf.h | 3 +++ 2 files changed, 25 insertions(+)
fs/xfs/libxfs/xfs_attr_leaf.c +22 -0
@@ -1316,6 +1316,28 @@ xfs_attr3_leaf_create(return 0 ; } +/* +* Reinitialize an existing attr fork block as an empty leaf, and attach +* the buffer to tp. +*/ +int +xfs_attr3_leaf_init ( +struct xfs_trans *tp, +struct xfs_inode *dp, +xfs_dablk_t blkno) +{ +struct xfs_buf *bp =NULL ; +struct xfs_da_args args = { + .trans = tp, + .dp = dp, + .owner = dp->i_ino, + .geo = dp->i_mount->m_attr_geo, + }; + +ASSERT (tp !=NULL ); + +return xfs_attr3_leaf_create (&args, blkno, &bp); +}/* * Split the leaf node, rebalance, then add the new entry. *
fs/xfs/libxfs/xfs_attr_leaf.h +3 -0
@@ -86,6 +86,9 @@ int xfs_attr3_leaf_list_int(struct xfs_buf *bp,/* * Routines used for shrinking the Btree. */ + +int xfs_attr3_leaf_init (struct xfs_trans *tp,struct xfs_inode *dp, +xfs_dablk_t blkno);int xfs_attr3_leaf_toosmall (struct xfs_da_state *state,int *retval); void xfs_attr3_leaf_unbalance(struct xfs_da_state *state, struct xfs_da_state_blk *drop_blk,
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/3 xfs: close crash window in attr dabtree inactivation
In reply to Sasha LevinFrom: Long Li <[email protected]>
[ Upstream commit b854e1c4eff3473b6d3a9ae74129ac5c48bc0b61 ]
When inactivating an inode with node-format extended attributes, xfs_attr3_node_inactive() invalidates all child leaf/node blocks via xfs_trans_binval(), but intentionally does not remove the corresponding entries from their parent node blocks. The implicit assumption is that xfs_attr_inactive() will truncate the entire attr fork to zero extents afterwards, so log recovery will never reach the root node and follow those stale pointers.
However, if a log shutdown occurs after the leaf/node block cancellations commit but before the attr bmap truncation commits, this assumption breaks. Recovery replays the attr bmap intact (the inode still has attr fork extents), but suppresses replay of all cancelled leaf/node blocks, maybe leaving them as stale data on disk. On the next mount, xlog_recover_process_iunlinks() retries inactivation and attempts to read the root node via the attr bmap. If the root node was not replayed, reading the unreplayed root block triggers a metadata verification failure immediately; if it was replayed, following its child pointers to unreplayed child blocks triggers the same failure:
XFS (pmem0): Metadata corruption detected at
xfs_da3_node_read_verify+0x53/0x220, xfs_da3_node block 0x78 XFS (pmem0): Unmount and run xfs_repair XFS (pmem0): First 128 bytes of corrupted metadata buffer: 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ XFS (pmem0): metadata I/O error in "xfs_da_read_buf+0x104/0x190" at daddr 0x78 len 8 error 117
Fix this in two places:
In xfs_attr3_node_inactive(), after calling xfs_trans_binval() on a child block, immediately remove the entry that references it from the parent node in the same transaction. This eliminates the window where the parent holds a pointer to a cancelled block. Once all children are removed, the now-empty root node is converted to a leaf block within the same transaction. This node-to-leaf conversion is necessary for crash safety. If the system shutdown after the empty node is written to the log but before the second-phase bmap truncation commits, log recovery will attempt to verify the root block on disk. xfs_da3_node_verify() does not permit a node block with count == 0; such a block will fail verification and trigger a metadata corruption shutdown. on the other hand, leaf blocks are allowed to have this transient state.
In xfs_attr_inactive(), split the attr fork truncation into two explicit phases. First, truncate all extents beyond the root block (the child extents whose parent references have already been removed above). Second, invalidate the root block and truncate the attr bmap to zero in a single transaction. The two operations in the second phase must be
atomic: as long as the attr bmap has any non-zero length, recovery can follow it to the root block, so the root block invalidation must commit together with the bmap-to-zero truncation.
--- fs/xfs/xfs_attr_inactive.c | 95 +++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 38 deletions(-)
fs/xfs/xfs_attr_inactive.c +57 -38
@@ -140,7 +140,7 @@ xfs_attr3_node_inactive(xfs_daddr_t parent_blkno, child_blkno;struct xfs_buf *child_bp;struct xfs_da3_icnode_hdr ichdr; -int error, i; +int error;/* * Since this code is recursive (gasp!) we must protect ourselves.
@@ -152,7 +152,7 @@ xfs_attr3_node_inactive(return -EFSCORRUPTED ; } -xfs_da3_node_hdr_from_disk (dp->i_mount, &ichdr, bp->b_addr); +xfs_da3_node_hdr_from_disk (mp, &ichdr, bp->b_addr); parent_blkno =xfs_buf_daddr (bp);if (!ichdr.count) {xfs_trans_brelse (*trans, bp);
@@ -167,7 +167,7 @@ xfs_attr3_node_inactive(* over the leaves removing all of them. If this is higher up * in the tree, recurse downward. */ -for (i =0 ; i < ichdr.count; i++) { +while (ichdr.count >0 ) {/* * Read the subsidiary block to see what we have to work with. * Don't do this in a transaction. This is a depth-first
@@ -218,29 +218,32 @@ xfs_attr3_node_inactive(xfs_trans_binval (*trans, child_bp); child_bp =NULL ; + error =xfs_da3_node_read_mapped (*trans, dp, + parent_blkno, &bp,XFS_ATTR_FORK ); +if (error) +return error; +/* -* If we're not done, re-read the parent to get the next -* child block number. +* Remove entry from parent node, prevents being indexed to. */ -if (i +1 < ichdr.count) { -struct xfs_da3_icnode_hdr phdr; +xfs_attr3_node_entry_remove (*trans, dp, bp,0 ); + +xfs_da3_node_hdr_from_disk (mp, &ichdr, bp->b_addr); + bp =NULL ; - error =xfs_da3_node_read_mapped (*trans, dp, - parent_blkno, &bp,XFS_ATTR_FORK ); +if (ichdr.count >0 ) { +/* +* If we're not done, get the next child block number. +*/ + child_fsb =be32_to_cpu (ichdr.btree[0 ].before); + +/* +* Atomically commit the whole invalidate stuff. +*/ + error =xfs_trans_roll_inode (trans, dp);if (error)return error; -xfs_da3_node_hdr_from_disk (dp->i_mount, &phdr, - bp->b_addr); - child_fsb =be32_to_cpu (phdr.btree[i +1 ].before); -xfs_trans_brelse (*trans, bp); - bp =NULL ; } -/* -* Atomically commit the whole invalidate stuff. -*/ - error =xfs_trans_roll_inode (trans, dp); -if (error) -return error; }return 0 ;
@@ -257,10 +260,8 @@ xfs_attr3_root_inactive(struct xfs_trans **trans, struct xfs_inode *dp) { - struct xfs_mount *mp = dp->i_mount; struct xfs_da_blkinfo *info;struct xfs_buf *bp; -xfs_daddr_t blkno;int error;/*
@@ -272,7 +273,6 @@ xfs_attr3_root_inactive( error =xfs_da3_node_read (*trans, dp,0 , &bp,XFS_ATTR_FORK );if (error)return error; - blkno =xfs_buf_daddr (bp);/* * Invalidate the tree, even if the "tree" is only a single leaf block.
@@ -283,10 +283,26 @@ xfs_attr3_root_inactive(case cpu_to_be16 (XFS_DA_NODE_MAGIC ):case cpu_to_be16 (XFS_DA3_NODE_MAGIC ): error =xfs_attr3_node_inactive (trans, dp, bp,1 ); +/* +* Empty root node block are not allowed, convert it to leaf. +*/ +if (!error) + error =xfs_attr3_leaf_init (*trans, dp,0 ); +if (!error) + error =xfs_trans_roll_inode (trans, dp);break ;case cpu_to_be16 (XFS_ATTR_LEAF_MAGIC ):case cpu_to_be16 (XFS_ATTR3_LEAF_MAGIC ): error =xfs_attr3_leaf_inactive (trans, dp, bp); +/* +* Reinit the leaf before truncating extents so that a crash +* mid-truncation leaves an empty leaf rather than one with +* entries that may reference freed remote value blocks. +*/ +if (!error) + error =xfs_attr3_leaf_init (*trans, dp,0 ); +if (!error) + error =xfs_trans_roll_inode (trans, dp);break ;default :xfs_dirattr_mark_sick (dp,XFS_ATTR_FORK );
@@ -295,21 +311,6 @@ xfs_attr3_root_inactive(xfs_trans_brelse (*trans, bp);break ; } -if (error) -return error; - -/* -* Invalidate the incore copy of the root block. -*/ - error =xfs_trans_get_buf (*trans, mp->m_ddev_targp, blkno, -XFS_FSB_TO_BB (mp, mp->m_attr_geo->fsbcount),0 , &bp); -if (error) -return error; -xfs_trans_binval (*trans, bp);/* remove from cache */ -/* -* Commit the invalidate and start the next transaction. -*/ - error =xfs_trans_roll_inode (trans, dp);return error; }
@@ -328,6 +329,7 @@ xfs_attr_inactive( {struct xfs_trans *trans;struct xfs_mount *mp; +struct xfs_buf *bp;int lock_mode =XFS_ILOCK_SHARED ;int error =0 ;
@@ -363,10 +365,27 @@ xfs_attr_inactive(* removal below. */ if (dp->i_af.if_nextents >0 ) { +/* +* Invalidate and truncate all blocks but leave the root block. +*/ error =xfs_attr3_root_inactive (&trans, dp);if (error) goto out_cancel; + error =xfs_itruncate_extents (&trans, dp,XFS_ATTR_FORK , +XFS_FSB_TO_B (mp, mp->m_attr_geo->fsbcount)); +if (error) + goto out_cancel; + +/* +* Invalidate and truncate the root block and ensure that the +* operation is completed within a single transaction. +*/ + error =xfs_da_get_buf (trans, dp,0 , &bp,XFS_ATTR_FORK ); +if (error) + goto out_cancel; + +xfs_trans_binval (trans, bp); error =xfs_itruncate_extents (&trans, dp,XFS_ATTR_FORK ,0 );if (error) goto out_cancel;
Signature · 1 line
2.53.0
Re: [PATCH 6.18.y 3/3] xfs: close crash window in attr dabtree inactivation
Quoted 61 lines · Sasha Levin
From: Long Li <[email protected]>
[ Upstream commit
b854e1c4eff3473b6d3a9ae74129ac5c48bc0b61]When inactivating an inode with node-format extended attributes,
xfs_attr3_node_inactive()invalidates all child leaf/node blocks viaxfs_trans_binval(), but intentionally does not remove the corresponding entries from their parent node blocks. The implicit assumption is thatxfs_attr_inactive()will truncate the entire attr fork to zero extents afterwards, so log recovery will never reach the root node and follow those stale pointers.However, if a log shutdown occurs after the leaf/node block cancellations commit but before the attr bmap truncation commits, this assumption breaks. Recovery replays the attr bmap intact (the inode still has attr fork extents), but suppresses replay of all cancelled leaf/node blocks, maybe leaving them as stale data on disk. On the next mount,
xlog_recover_process_iunlinks()retries inactivation and attempts to read the root node via the attr bmap. If the root node was not replayed, reading the unreplayed root block triggers a metadata verification failure immediately; if it was replayed, following its child pointers to unreplayed child blocks triggers the same failure:XFS (pmem0): Metadata corruption detected at
xfs_da3_node_read_verify+0x53/0x220, xfs_da3_node block 0x78 XFS (pmem0): Unmount and run xfs_repair XFS (pmem0): First 128 bytes of corrupted metadata buffer: 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ XFS (pmem0): metadata I/O error in "xfs_da_read_buf+0x104/0x190" at daddr 0x78 len 8 error 117Fix this in two places:
In
xfs_attr3_node_inactive(), after callingxfs_trans_binval()on a child block, immediately remove the entry that references it from the parent node in the same transaction. This eliminates the window where the parent holds a pointer to a cancelled block. Once all children are removed, the now-empty root node is converted to a leaf block within the same transaction. This node-to-leaf conversion is necessary for crash safety. If the system shutdown after the empty node is written to the log but before the second-phase bmap truncation commits, log recovery will attempt to verify the root block on disk.xfs_da3_node_verify()does not permit a node block with count == 0; such a block will fail verification and trigger a metadata corruption shutdown. on the other hand, leaf blocks are allowed to have this transient state.In
xfs_attr_inactive(), split the attr fork truncation into two explicit phases. First, truncate all extents beyond the root block (the child extents whose parent references have already been removed above). Second, invalidate the root block and truncate the attr bmap to zero in a single transaction. The two operations in the second phase must be
atomic: as long as the attr bmap has any non-zero length, recovery can follow it to the root block, so the root block invalidation must commit together with the bmap-to-zero truncation.
Quoted 6 lines
Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Long Li <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Carlos Maiolino <[email protected]>Signed-off-by Sasha Levin <[email protected]>
Hi Sasha,
The current patch depends on e942498385bf ("xfs: only assert new size for datafork during truncate extents"), otherwise it will trigger an assertion.
Thanks,
Long Li