Skip to content

Commit ff4ab5e

Browse files
committed
xfs: fix an incore inode UAF in xfs_bui_recover
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 <[email protected]> Reviewed-by: Brian Foster <[email protected]> Reviewed-by: Christoph Hellwig <[email protected]>
1 parent 64a3f33 commit ff4ab5e

File tree

7 files changed

+61
-13
lines changed

7 files changed

+61
-13
lines changed

fs/xfs/libxfs/xfs_defer.c

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "xfs_inode.h"
1717
#include "xfs_inode_item.h"
1818
#include "xfs_trace.h"
19+
#include "xfs_icache.h"
1920

2021
/*
2122
* Deferred Operations in XFS
@@ -553,10 +554,14 @@ xfs_defer_move(
553554
* deferred ops state is transferred to the capture structure and the
554555
* transaction is then ready for the caller to commit it. If there are no
555556
* intent items to capture, this function returns NULL.
557+
*
558+
* If capture_ip is not NULL, the capture structure will obtain an extra
559+
* reference to the inode.
556560
*/
557561
static struct xfs_defer_capture *
558562
xfs_defer_ops_capture(
559-
struct xfs_trans *tp)
563+
struct xfs_trans *tp,
564+
struct xfs_inode *capture_ip)
560565
{
561566
struct xfs_defer_capture *dfc;
562567

@@ -582,6 +587,15 @@ xfs_defer_ops_capture(
582587
/* Preserve the log reservation size. */
583588
dfc->dfc_logres = tp->t_log_res;
584589

590+
/*
591+
* Grab an extra reference to this inode and attach it to the capture
592+
* structure.
593+
*/
594+
if (capture_ip) {
595+
ihold(VFS_I(capture_ip));
596+
dfc->dfc_capture_ip = capture_ip;
597+
}
598+
585599
return dfc;
586600
}
587601

@@ -592,24 +606,33 @@ xfs_defer_ops_release(
592606
struct xfs_defer_capture *dfc)
593607
{
594608
xfs_defer_cancel_list(mp, &dfc->dfc_dfops);
609+
if (dfc->dfc_capture_ip)
610+
xfs_irele(dfc->dfc_capture_ip);
595611
kmem_free(dfc);
596612
}
597613

598614
/*
599615
* Capture any deferred ops and commit the transaction. This is the last step
600-
* needed to finish a log intent item that we recovered from the log.
616+
* needed to finish a log intent item that we recovered from the log. If any
617+
* of the deferred ops operate on an inode, the caller must pass in that inode
618+
* so that the reference can be transferred to the capture structure. The
619+
* caller must hold ILOCK_EXCL on the inode, and must unlock it before calling
620+
* xfs_defer_ops_continue.
601621
*/
602622
int
603623
xfs_defer_ops_capture_and_commit(
604624
struct xfs_trans *tp,
625+
struct xfs_inode *capture_ip,
605626
struct list_head *capture_list)
606627
{
607628
struct xfs_mount *mp = tp->t_mountp;
608629
struct xfs_defer_capture *dfc;
609630
int error;
610631

632+
ASSERT(!capture_ip || xfs_isilocked(capture_ip, XFS_ILOCK_EXCL));
633+
611634
/* If we don't capture anything, commit transaction and exit. */
612-
dfc = xfs_defer_ops_capture(tp);
635+
dfc = xfs_defer_ops_capture(tp, capture_ip);
613636
if (!dfc)
614637
return xfs_trans_commit(tp);
615638

@@ -626,16 +649,26 @@ xfs_defer_ops_capture_and_commit(
626649

627650
/*
628651
* Attach a chain of captured deferred ops to a new transaction and free the
629-
* capture structure.
652+
* capture structure. If an inode was captured, it will be passed back to the
653+
* caller with ILOCK_EXCL held and joined to the transaction with lockflags==0.
654+
* The caller now owns the inode reference.
630655
*/
631656
void
632657
xfs_defer_ops_continue(
633658
struct xfs_defer_capture *dfc,
634-
struct xfs_trans *tp)
659+
struct xfs_trans *tp,
660+
struct xfs_inode **captured_ipp)
635661
{
636662
ASSERT(tp->t_flags & XFS_TRANS_PERM_LOG_RES);
637663
ASSERT(!(tp->t_flags & XFS_TRANS_DIRTY));
638664

665+
/* Lock and join the captured inode to the new transaction. */
666+
if (dfc->dfc_capture_ip) {
667+
xfs_ilock(dfc->dfc_capture_ip, XFS_ILOCK_EXCL);
668+
xfs_trans_ijoin(tp, dfc->dfc_capture_ip, 0);
669+
}
670+
*captured_ipp = dfc->dfc_capture_ip;
671+
639672
/* Move captured dfops chain and state to the transaction. */
640673
list_splice_init(&dfc->dfc_dfops, &tp->t_dfops);
641674
tp->t_flags |= dfc->dfc_tpflags;

fs/xfs/libxfs/xfs_defer.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,22 @@ struct xfs_defer_capture {
8282

8383
/* Log reservation saved from the transaction. */
8484
unsigned int dfc_logres;
85+
86+
/*
87+
* An inode reference that must be maintained to complete the deferred
88+
* work.
89+
*/
90+
struct xfs_inode *dfc_capture_ip;
8591
};
8692

8793
/*
8894
* Functions to capture a chain of deferred operations and continue them later.
8995
* This doesn't normally happen except log recovery.
9096
*/
9197
int xfs_defer_ops_capture_and_commit(struct xfs_trans *tp,
92-
struct list_head *capture_list);
93-
void xfs_defer_ops_continue(struct xfs_defer_capture *d, struct xfs_trans *tp);
98+
struct xfs_inode *capture_ip, struct list_head *capture_list);
99+
void xfs_defer_ops_continue(struct xfs_defer_capture *d, struct xfs_trans *tp,
100+
struct xfs_inode **captured_ipp);
94101
void xfs_defer_ops_release(struct xfs_mount *mp, struct xfs_defer_capture *d);
95102

96103
#endif /* __XFS_DEFER_H__ */

fs/xfs/xfs_bmap_item.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,8 +513,11 @@ xfs_bui_item_recover(
513513
xfs_bmap_unmap_extent(tp, ip, &irec);
514514
}
515515

516-
/* Commit transaction, which frees the transaction. */
517-
error = xfs_defer_ops_capture_and_commit(tp, capture_list);
516+
/*
517+
* Commit transaction, which frees the transaction and saves the inode
518+
* for later replay activities.
519+
*/
520+
error = xfs_defer_ops_capture_and_commit(tp, ip, capture_list);
518521
if (error)
519522
goto err_unlock;
520523

fs/xfs/xfs_extfree_item.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ xfs_efi_item_recover(
627627

628628
}
629629

630-
return xfs_defer_ops_capture_and_commit(tp, capture_list);
630+
return xfs_defer_ops_capture_and_commit(tp, NULL, capture_list);
631631

632632
abort_error:
633633
xfs_trans_cancel(tp);

fs/xfs/xfs_log_recover.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2439,6 +2439,7 @@ xlog_finish_defer_ops(
24392439
{
24402440
struct xfs_defer_capture *dfc, *next;
24412441
struct xfs_trans *tp;
2442+
struct xfs_inode *ip;
24422443
int error = 0;
24432444

24442445
list_for_each_entry_safe(dfc, next, capture_list, dfc_list) {
@@ -2464,9 +2465,13 @@ xlog_finish_defer_ops(
24642465
* from recovering a single intent item.
24652466
*/
24662467
list_del_init(&dfc->dfc_list);
2467-
xfs_defer_ops_continue(dfc, tp);
2468+
xfs_defer_ops_continue(dfc, tp, &ip);
24682469

24692470
error = xfs_trans_commit(tp);
2471+
if (ip) {
2472+
xfs_iunlock(ip, XFS_ILOCK_EXCL);
2473+
xfs_irele(ip);
2474+
}
24702475
if (error)
24712476
return error;
24722477
}

fs/xfs/xfs_refcount_item.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ xfs_cui_item_recover(
544544
}
545545

546546
xfs_refcount_finish_one_cleanup(tp, rcur, error);
547-
return xfs_defer_ops_capture_and_commit(tp, capture_list);
547+
return xfs_defer_ops_capture_and_commit(tp, NULL, capture_list);
548548

549549
abort_error:
550550
xfs_refcount_finish_one_cleanup(tp, rcur, error);

fs/xfs/xfs_rmap_item.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ xfs_rui_item_recover(
567567
}
568568

569569
xfs_rmap_finish_one_cleanup(tp, rcur, error);
570-
return xfs_defer_ops_capture_and_commit(tp, capture_list);
570+
return xfs_defer_ops_capture_and_commit(tp, NULL, capture_list);
571571

572572
abort_error:
573573
xfs_rmap_finish_one_cleanup(tp, rcur, error);

0 commit comments

Comments
 (0)