Skip to content

Commit 1bc2e30

Browse files
djwongsmb49
authored andcommitted
xfs: fix an incore inode UAF in xfs_bui_recover
BugLink: https://bugs.launchpad.net/bugs/2011625 commit ff4ab5e upstream. 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> Reviewed-by: Brian Foster <bfoster@redhat.com> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Chandan Babu R <chandan.babu@oracle.com> Acked-by: Darrick J. Wong <djwong@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Luke Nowakowski-Krijger <luke.nowakowskikrijger@canonical.com> Acked-by: Kamal Mostafa <kamal@canonical.com> Signed-off-by: Stefan Bader <stefan.bader@canonical.com>
1 parent 04bfe13 commit 1bc2e30

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
@@ -567,10 +568,14 @@ xfs_defer_move(
567568
* deferred ops state is transferred to the capture structure and the
568569
* transaction is then ready for the caller to commit it. If there are no
569570
* intent items to capture, this function returns NULL.
571+
*
572+
* If capture_ip is not NULL, the capture structure will obtain an extra
573+
* reference to the inode.
570574
*/
571575
static struct xfs_defer_capture *
572576
xfs_defer_ops_capture(
573-
struct xfs_trans *tp)
577+
struct xfs_trans *tp,
578+
struct xfs_inode *capture_ip)
574579
{
575580
struct xfs_defer_capture *dfc;
576581

@@ -596,6 +601,15 @@ xfs_defer_ops_capture(
596601
/* Preserve the log reservation size. */
597602
dfc->dfc_logres = tp->t_log_res;
598603

604+
/*
605+
* Grab an extra reference to this inode and attach it to the capture
606+
* structure.
607+
*/
608+
if (capture_ip) {
609+
ihold(VFS_I(capture_ip));
610+
dfc->dfc_capture_ip = capture_ip;
611+
}
612+
599613
return dfc;
600614
}
601615

@@ -606,24 +620,33 @@ xfs_defer_ops_release(
606620
struct xfs_defer_capture *dfc)
607621
{
608622
xfs_defer_cancel_list(mp, &dfc->dfc_dfops);
623+
if (dfc->dfc_capture_ip)
624+
xfs_irele(dfc->dfc_capture_ip);
609625
kmem_free(dfc);
610626
}
611627

612628
/*
613629
* Capture any deferred ops and commit the transaction. This is the last step
614-
* needed to finish a log intent item that we recovered from the log.
630+
* needed to finish a log intent item that we recovered from the log. If any
631+
* of the deferred ops operate on an inode, the caller must pass in that inode
632+
* so that the reference can be transferred to the capture structure. The
633+
* caller must hold ILOCK_EXCL on the inode, and must unlock it before calling
634+
* xfs_defer_ops_continue.
615635
*/
616636
int
617637
xfs_defer_ops_capture_and_commit(
618638
struct xfs_trans *tp,
639+
struct xfs_inode *capture_ip,
619640
struct list_head *capture_list)
620641
{
621642
struct xfs_mount *mp = tp->t_mountp;
622643
struct xfs_defer_capture *dfc;
623644
int error;
624645

646+
ASSERT(!capture_ip || xfs_isilocked(capture_ip, XFS_ILOCK_EXCL));
647+
625648
/* If we don't capture anything, commit transaction and exit. */
626-
dfc = xfs_defer_ops_capture(tp);
649+
dfc = xfs_defer_ops_capture(tp, capture_ip);
627650
if (!dfc)
628651
return xfs_trans_commit(tp);
629652

@@ -640,16 +663,26 @@ xfs_defer_ops_capture_and_commit(
640663

641664
/*
642665
* Attach a chain of captured deferred ops to a new transaction and free the
643-
* capture structure.
666+
* capture structure. If an inode was captured, it will be passed back to the
667+
* caller with ILOCK_EXCL held and joined to the transaction with lockflags==0.
668+
* The caller now owns the inode reference.
644669
*/
645670
void
646671
xfs_defer_ops_continue(
647672
struct xfs_defer_capture *dfc,
648-
struct xfs_trans *tp)
673+
struct xfs_trans *tp,
674+
struct xfs_inode **captured_ipp)
649675
{
650676
ASSERT(tp->t_flags & XFS_TRANS_PERM_LOG_RES);
651677
ASSERT(!(tp->t_flags & XFS_TRANS_DIRTY));
652678

679+
/* Lock and join the captured inode to the new transaction. */
680+
if (dfc->dfc_capture_ip) {
681+
xfs_ilock(dfc->dfc_capture_ip, XFS_ILOCK_EXCL);
682+
xfs_trans_ijoin(tp, dfc->dfc_capture_ip, 0);
683+
}
684+
*captured_ipp = dfc->dfc_capture_ip;
685+
653686
/* Move captured dfops chain and state to the transaction. */
654687
list_splice_init(&dfc->dfc_dfops, &tp->t_dfops);
655688
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
@@ -80,15 +80,22 @@ struct xfs_defer_capture {
8080

8181
/* Log reservation saved from the transaction. */
8282
unsigned int dfc_logres;
83+
84+
/*
85+
* An inode reference that must be maintained to complete the deferred
86+
* work.
87+
*/
88+
struct xfs_inode *dfc_capture_ip;
8389
};
8490

8591
/*
8692
* Functions to capture a chain of deferred operations and continue them later.
8793
* This doesn't normally happen except log recovery.
8894
*/
8995
int xfs_defer_ops_capture_and_commit(struct xfs_trans *tp,
90-
struct list_head *capture_list);
91-
void xfs_defer_ops_continue(struct xfs_defer_capture *d, struct xfs_trans *tp);
96+
struct xfs_inode *capture_ip, struct list_head *capture_list);
97+
void xfs_defer_ops_continue(struct xfs_defer_capture *d, struct xfs_trans *tp,
98+
struct xfs_inode **captured_ipp);
9299
void xfs_defer_ops_release(struct xfs_mount *mp, struct xfs_defer_capture *d);
93100

94101
#endif /* __XFS_DEFER_H__ */

fs/xfs/xfs_bmap_item.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,11 @@ xfs_bui_recover(
528528
}
529529

530530
set_bit(XFS_BUI_RECOVERED, &buip->bui_flags);
531-
/* Commit transaction, which frees the transaction. */
532-
error = xfs_defer_ops_capture_and_commit(tp, capture_list);
531+
/*
532+
* Commit transaction, which frees the transaction and saves the inode
533+
* for later replay activities.
534+
*/
535+
error = xfs_defer_ops_capture_and_commit(tp, ip, capture_list);
533536
if (error)
534537
goto err_unlock;
535538

fs/xfs/xfs_extfree_item.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ xfs_efi_recover(
639639

640640
set_bit(XFS_EFI_RECOVERED, &efip->efi_flags);
641641

642-
return xfs_defer_ops_capture_and_commit(tp, capture_list);
642+
return xfs_defer_ops_capture_and_commit(tp, NULL, capture_list);
643643

644644
abort_error:
645645
xfs_trans_cancel(tp);

fs/xfs/xfs_log_recover.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4766,6 +4766,7 @@ xlog_finish_defer_ops(
47664766
{
47674767
struct xfs_defer_capture *dfc, *next;
47684768
struct xfs_trans *tp;
4769+
struct xfs_inode *ip;
47694770
int error = 0;
47704771

47714772
list_for_each_entry_safe(dfc, next, capture_list, dfc_list) {
@@ -4791,9 +4792,13 @@ xlog_finish_defer_ops(
47914792
* from recovering a single intent item.
47924793
*/
47934794
list_del_init(&dfc->dfc_list);
4794-
xfs_defer_ops_continue(dfc, tp);
4795+
xfs_defer_ops_continue(dfc, tp, &ip);
47954796

47964797
error = xfs_trans_commit(tp);
4798+
if (ip) {
4799+
xfs_iunlock(ip, XFS_ILOCK_EXCL);
4800+
xfs_irele(ip);
4801+
}
47974802
if (error)
47984803
return error;
47994804
}

fs/xfs/xfs_refcount_item.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ xfs_cui_recover(
569569

570570
xfs_refcount_finish_one_cleanup(tp, rcur, error);
571571
set_bit(XFS_CUI_RECOVERED, &cuip->cui_flags);
572-
return xfs_defer_ops_capture_and_commit(tp, capture_list);
572+
return xfs_defer_ops_capture_and_commit(tp, NULL, capture_list);
573573

574574
abort_error:
575575
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
@@ -593,7 +593,7 @@ xfs_rui_recover(
593593

594594
xfs_rmap_finish_one_cleanup(tp, rcur, error);
595595
set_bit(XFS_RUI_RECOVERED, &ruip->rui_flags);
596-
return xfs_defer_ops_capture_and_commit(tp, capture_list);
596+
return xfs_defer_ops_capture_and_commit(tp, NULL, capture_list);
597597

598598
abort_error:
599599
xfs_rmap_finish_one_cleanup(tp, rcur, error);

0 commit comments

Comments
 (0)