summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <darrick.wong@oracle.com>2020-04-29 11:50:48 -0700
committerDarrick J. Wong <darrick.wong@oracle.com>2020-05-06 17:58:40 -0700
commit6edd73db978f1c4c84c3187080eacb33f03523d2 (patch)
tree3f1b18036abee77082dfe708329aee67a2e3d799
parent3aafa39d3758487f6c8813d3035b09ea655ef165 (diff)
xfs: fix an incore inode UAF in xfs_bui_recoverfix-log-recovery_2020-05-06
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>
-rw-r--r--fs/xfs/libxfs/xfs_defer.c50
-rw-r--r--fs/xfs/libxfs/xfs_defer.h10
-rw-r--r--fs/xfs/xfs_bmap_item.c7
-rw-r--r--fs/xfs/xfs_icache.c19
4 files changed, 83 insertions, 3 deletions
diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c
index 1964ebbe21d3..853983b7eac5 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
@@ -582,9 +583,20 @@ xfs_defer_continue(
struct xfs_defer_capture *dfc,
struct xfs_trans *tp)
{
+ int i;
+
ASSERT(tp->t_flags & XFS_TRANS_PERM_LOG_RES);
ASSERT(!(tp->t_flags & XFS_TRANS_DIRTY));
+ /* Re-acquire the inode locks. */
+ for (i = 0; i < XFS_DEFER_CAPTURE_INODES; i++) {
+ if (!dfc->dfc_inodes[i])
+ break;
+
+ dfc->dfc_ilocks[i] = XFS_ILOCK_EXCL;
+ xfs_ilock(dfc->dfc_inodes[i], dfc->dfc_ilocks[i]);
+ }
+
/* Add the dfops items to the transaction. */
list_splice_init(&dfc->dfc_dfops, &tp->t_dfops);
tp->t_flags |= dfc->dfc_tpflags;
@@ -597,5 +609,43 @@ xfs_defer_capture_free(
struct xfs_defer_capture *dfc)
{
xfs_defer_cancel_list(mp, &dfc->dfc_dfops);
+ xfs_defer_capture_irele(dfc);
kmem_free(dfc);
}
+
+/*
+ * Capture an inode along with the deferred ops freezer. Callers must hold
+ * ILOCK_EXCL, which will be dropped and reacquired when we're ready to
+ * continue replaying the deferred ops.
+ */
+int
+xfs_defer_capture_inode(
+ struct xfs_defer_capture *dfc,
+ struct xfs_inode *ip)
+{
+ unsigned int i;
+
+ ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL));
+
+ for (i = 0; i < XFS_DEFER_CAPTURE_INODES; i++) {
+ if (dfc->dfc_inodes[i] == ip)
+ goto out;
+ if (dfc->dfc_inodes[i] == NULL)
+ break;
+ }
+
+ if (i == XFS_DEFER_CAPTURE_INODES) {
+ ASSERT(0);
+ return -EFSCORRUPTED;
+ }
+
+ /*
+ * Attach this inode to the freezer and drop its ILOCK because we
+ * assume the caller will need to allocate a transaction.
+ */
+ dfc->dfc_inodes[i] = ip;
+ dfc->dfc_ilocks[i] = 0;
+out:
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ return 0;
+}
diff --git a/fs/xfs/libxfs/xfs_defer.h b/fs/xfs/libxfs/xfs_defer.h
index 4ea287fa2226..51dd5038e730 100644
--- a/fs/xfs/libxfs/xfs_defer.h
+++ b/fs/xfs/libxfs/xfs_defer.h
@@ -76,6 +76,11 @@ struct xfs_defer_capture {
/* Deferred ops state saved from the transaction. */
struct list_head dfc_dfops;
unsigned int dfc_tpflags;
+
+ /* Inodes to hold when we want to finish the deferred work items. */
+#define XFS_DEFER_CAPTURE_INODES 2
+ unsigned int dfc_ilocks[XFS_DEFER_CAPTURE_INODES];
+ struct xfs_inode *dfc_inodes[XFS_DEFER_CAPTURE_INODES];
};
/*
@@ -86,5 +91,10 @@ int xfs_defer_capture(struct xfs_trans *tp, struct xfs_defer_capture **dfcp);
void xfs_defer_continue(struct xfs_defer_capture *dfc, struct xfs_trans *tp);
void xfs_defer_capture_free(struct xfs_mount *mp,
struct xfs_defer_capture *dfc);
+int xfs_defer_capture_inode(struct xfs_defer_capture *dfc,
+ struct xfs_inode *ip);
+
+/* These functions must be provided by the xfs implementation. */
+void xfs_defer_capture_irele(struct xfs_defer_capture *dfc);
#endif /* __XFS_DEFER_H__ */
diff --git a/fs/xfs/xfs_bmap_item.c b/fs/xfs/xfs_bmap_item.c
index fd56f354cf20..b9bcdb84bdfe 100644
--- a/fs/xfs/xfs_bmap_item.c
+++ b/fs/xfs/xfs_bmap_item.c
@@ -530,12 +530,13 @@ xfs_bui_item_recover(
}
error = xlog_recover_trans_commit(tp, dfcp);
- xfs_iunlock(ip, XFS_ILOCK_EXCL);
- xfs_irele(ip);
- return error;
+ if (error)
+ goto err_rele;
+ return xfs_defer_capture_inode(*dfcp, ip);
err_inode:
xfs_trans_cancel(tp);
+err_rele:
if (ip) {
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xfs_irele(ip);
diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c
index 922a29032e37..87804e841312 100644
--- a/fs/xfs/xfs_icache.c
+++ b/fs/xfs/xfs_icache.c
@@ -12,6 +12,7 @@
#include "xfs_sb.h"
#include "xfs_mount.h"
#include "xfs_inode.h"
+#include "xfs_defer.h"
#include "xfs_trans.h"
#include "xfs_trans_priv.h"
#include "xfs_inode_item.h"
@@ -1847,3 +1848,21 @@ xfs_start_block_reaping(
xfs_queue_eofblocks(mp);
xfs_queue_cowblocks(mp);
}
+
+/* Release all the inode resources attached to this dfops capture device. */
+void
+xfs_defer_capture_irele(
+ struct xfs_defer_capture *dfc)
+{
+ unsigned int i;
+
+ for (i = 0; i < XFS_DEFER_CAPTURE_INODES; i++) {
+ if (!dfc->dfc_inodes[i])
+ break;
+
+ if (dfc->dfc_ilocks[i])
+ xfs_iunlock(dfc->dfc_inodes[i], dfc->dfc_ilocks[i]);
+ xfs_irele(dfc->dfc_inodes[i]);
+ dfc->dfc_inodes[i] = NULL;
+ }
+}