diff options
author | Darrick J. Wong <darrick.wong@oracle.com> | 2020-06-25 18:18:55 -0700 |
---|---|---|
committer | Darrick J. Wong <darrick.wong@oracle.com> | 2020-09-14 19:10:43 -0700 |
commit | 5cd779f671fc7c7e802a7e26bbf513bc02da80a1 (patch) | |
tree | 53cf893f49f9a041ea13545937c32b8380ce37ab /fs/xfs/libxfs/xfs_defer.c | |
parent | 4e5df2c4b07f7e8ed6dd7ed188fb9d26d58ab611 (diff) |
xfs: fix an incore inode UAF in xfs_bui_recoverfix-log-recovery_2020-09-14
In xfs_bui_item_recover, there exists a use-after-free bug with regards
to the inode that is involved in the bmap replay operation. If the
mapping operation does not complete, we call xfs_bmap_unmap_extent to
create a deferred op to finish the unmapping work, and we retain a
pointer to the incore inode.
Unfortunately, the very next thing we do is commit the transaction and
drop the inode. If reclaim tears down the inode before we try to finish
the defer ops, we dereference garbage and blow up. Therefore, create a
way to join inodes to the defer ops freezer so that we can maintain the
xfs_inode reference until we're done with the inode.
Note: This imposes the requirement that there be enough memory to keep
every incore inode in memory throughout recovery.
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Diffstat (limited to 'fs/xfs/libxfs/xfs_defer.c')
-rw-r--r-- | fs/xfs/libxfs/xfs_defer.c | 57 |
1 files changed, 54 insertions, 3 deletions
diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c index edfb3b9856c8..97523b394932 100644 --- a/fs/xfs/libxfs/xfs_defer.c +++ b/fs/xfs/libxfs/xfs_defer.c @@ -16,6 +16,7 @@ #include "xfs_inode.h" #include "xfs_inode_item.h" #include "xfs_trace.h" +#include "xfs_icache.h" /* * Deferred Operations in XFS @@ -555,6 +556,18 @@ xfs_defer_move( xfs_defer_reset(stp); } +/* Unlock the inodes attached to this dfops capture device. */ +static void +xfs_defer_capture_iunlock( + struct xfs_defer_capture *dfc) +{ + unsigned int i; + + for (i = 0; i < XFS_DEFER_OPS_NR_INODES && dfc->dfc_inodes[i]; i++) + xfs_iunlock(dfc->dfc_inodes[i], XFS_ILOCK_EXCL); + dfc->dfc_ilocked = false; +} + /* * Prepare a chain of fresh deferred ops work items to be completed later. Log * recovery requires the ability to put off until later the actual finishing @@ -568,14 +581,21 @@ xfs_defer_move( * transaction is committed. * * Note that the capture state is passed up to the caller and must be freed - * even if the transaction commit returns error. + * even if the transaction commit returns error. If inodes were passed in and + * a state capture structure was returned, the inodes are now owned by the + * state capture structure and the caller must not touch the inodes. + * + * If no structure is returned, the caller still owns the inodes. */ int xfs_defer_capture( struct xfs_trans *tp, - struct xfs_defer_capture **dfcp) + struct xfs_defer_capture **dfcp, + struct xfs_inode *ip1, + struct xfs_inode *ip2) { struct xfs_defer_capture *dfc = NULL; + int error; if (!list_empty(&tp->t_dfops)) { dfc = kmem_zalloc(sizeof(*dfc), KM_NOFS); @@ -594,10 +614,31 @@ xfs_defer_capture( dfc->dfc_tres.tr_logcount = tp->t_log_count; dfc->dfc_tres.tr_logflags = XFS_TRANS_PERM_LOG_RES; xfs_defer_reset(tp); + + /* + * Transfer responsibility for unlocking and releasing the + * inodes to the capture structure. + */ + if (!ip1) + ip1 = ip2; + dfc->dfc_ilocked = true; + dfc->dfc_inodes[0] = ip1; + if (ip2 != ip1) + dfc->dfc_inodes[1] = ip2; } *dfcp = dfc; - return xfs_trans_commit(tp); + error = xfs_trans_commit(tp); + if (error) + return error; + + /* + * Now that we've committed the transaction, it's safe to unlock the + * inodes that were passed in if we've taken over their ownership. + */ + if (dfc) + xfs_defer_capture_iunlock(dfc); + return 0; } /* Attach a chain of captured deferred ops to a new transaction. */ @@ -609,6 +650,13 @@ xfs_defer_continue( ASSERT(tp->t_flags & XFS_TRANS_PERM_LOG_RES); ASSERT(!(tp->t_flags & XFS_TRANS_DIRTY)); + /* Lock and join the inodes to the new transaction. */ + xfs_defer_continue_inodes(dfc, tp); + if (dfc->dfc_inodes[0]) + xfs_trans_ijoin(tp, dfc->dfc_inodes[0], 0); + if (dfc->dfc_inodes[1]) + xfs_trans_ijoin(tp, dfc->dfc_inodes[1], 0); + /* Move captured dfops chain and state to the transaction. */ list_splice_init(&dfc->dfc_dfops, &tp->t_dfops); tp->t_flags |= dfc->dfc_tpflags; @@ -624,5 +672,8 @@ xfs_defer_capture_free( struct xfs_defer_capture *dfc) { xfs_defer_cancel_list(mp, &dfc->dfc_dfops); + if (dfc->dfc_ilocked) + xfs_defer_capture_iunlock(dfc); + xfs_defer_capture_irele(dfc); kmem_free(dfc); } |