提交 3f0a724e 编写于 作者: D Darrick J. Wong 提交者: Zheng Zengkai

xfs: avoid buffer deadlocks when walking fs inodes

mainline-inclusion
from mainline-v5.14-rc4
commit a6343e4d
category: bugfix
bugzilla: https://gitee.com/openeuler/kernel/issues/I4KIAO
CVE: NA

Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a6343e4d9278b3919c809fab9945c4d8f04fadf5

-------------------------------------------------

When we're servicing an INUMBERS or BULKSTAT request or running
quotacheck, grab an empty transaction so that we can use its inherent
recursive buffer locking abilities to detect inode btree cycles without
hitting ABBA buffer deadlocks.  This patch requires the deferred inode
inactivation patchset because xfs_irele cannot directly call
xfs_inactive when the iwalk itself has an (empty) transaction.

Found by fuzzing an inode btree pointer to introduce a cycle into the
tree (xfs/365).
Signed-off-by: NDarrick J. Wong <djwong@kernel.org>
Reviewed-by: NDave Chinner <dchinner@redhat.com>
Reviewed-by: NChristoph Hellwig <hch@lst.de>
Signed-off-by: NLihong Kou <koulihong@huawei.com>
Reviewed-by: NZhang Yi <yi.zhang@huawei.com>
Signed-off-by: NZheng Zengkai <zhengzengkai@huawei.com>
上级 70214919
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "xfs_error.h" #include "xfs_error.h"
#include "xfs_icache.h" #include "xfs_icache.h"
#include "xfs_health.h" #include "xfs_health.h"
#include "xfs_trans.h"
/* /*
* Bulk Stat * Bulk Stat
...@@ -164,6 +165,7 @@ xfs_bulkstat_one( ...@@ -164,6 +165,7 @@ xfs_bulkstat_one(
.formatter = formatter, .formatter = formatter,
.breq = breq, .breq = breq,
}; };
struct xfs_trans *tp;
int error; int error;
ASSERT(breq->icount == 1); ASSERT(breq->icount == 1);
...@@ -173,8 +175,17 @@ xfs_bulkstat_one( ...@@ -173,8 +175,17 @@ xfs_bulkstat_one(
if (!bc.buf) if (!bc.buf)
return -ENOMEM; return -ENOMEM;
error = xfs_bulkstat_one_int(breq->mp, NULL, breq->startino, &bc); /*
* Grab an empty transaction so that we can use its recursive buffer
* locking abilities to detect cycles in the inobt without deadlocking.
*/
error = xfs_trans_alloc_empty(breq->mp, &tp);
if (error)
goto out;
error = xfs_bulkstat_one_int(breq->mp, tp, breq->startino, &bc);
xfs_trans_cancel(tp);
out:
kmem_free(bc.buf); kmem_free(bc.buf);
/* /*
...@@ -237,6 +248,7 @@ xfs_bulkstat( ...@@ -237,6 +248,7 @@ xfs_bulkstat(
.formatter = formatter, .formatter = formatter,
.breq = breq, .breq = breq,
}; };
struct xfs_trans *tp;
int error; int error;
if (xfs_bulkstat_already_done(breq->mp, breq->startino)) if (xfs_bulkstat_already_done(breq->mp, breq->startino))
...@@ -247,9 +259,18 @@ xfs_bulkstat( ...@@ -247,9 +259,18 @@ xfs_bulkstat(
if (!bc.buf) if (!bc.buf)
return -ENOMEM; return -ENOMEM;
error = xfs_iwalk(breq->mp, NULL, breq->startino, breq->flags, /*
xfs_bulkstat_iwalk, breq->icount, &bc); * Grab an empty transaction so that we can use its recursive buffer
* locking abilities to detect cycles in the inobt without deadlocking.
*/
error = xfs_trans_alloc_empty(breq->mp, &tp);
if (error)
goto out;
error = xfs_iwalk(breq->mp, tp, breq->startino, breq->flags,
xfs_bulkstat_iwalk, breq->icount, &bc);
xfs_trans_cancel(tp);
out:
kmem_free(bc.buf); kmem_free(bc.buf);
/* /*
...@@ -362,13 +383,24 @@ xfs_inumbers( ...@@ -362,13 +383,24 @@ xfs_inumbers(
.formatter = formatter, .formatter = formatter,
.breq = breq, .breq = breq,
}; };
struct xfs_trans *tp;
int error = 0; int error = 0;
if (xfs_bulkstat_already_done(breq->mp, breq->startino)) if (xfs_bulkstat_already_done(breq->mp, breq->startino))
return 0; return 0;
error = xfs_inobt_walk(breq->mp, NULL, breq->startino, breq->flags, /*
* Grab an empty transaction so that we can use its recursive buffer
* locking abilities to detect cycles in the inobt without deadlocking.
*/
error = xfs_trans_alloc_empty(breq->mp, &tp);
if (error)
goto out;
error = xfs_inobt_walk(breq->mp, tp, breq->startino, breq->flags,
xfs_inumbers_walk, breq->icount, &ic); xfs_inumbers_walk, breq->icount, &ic);
xfs_trans_cancel(tp);
out:
/* /*
* We found some inode groups, so clear the error status and return * We found some inode groups, so clear the error status and return
......
...@@ -81,6 +81,9 @@ struct xfs_iwalk_ag { ...@@ -81,6 +81,9 @@ struct xfs_iwalk_ag {
/* Skip empty inobt records? */ /* Skip empty inobt records? */
unsigned int skip_empty:1; unsigned int skip_empty:1;
/* Drop the (hopefully empty) transaction when calling iwalk_fn. */
unsigned int drop_trans:1;
}; };
/* /*
...@@ -351,7 +354,6 @@ xfs_iwalk_run_callbacks( ...@@ -351,7 +354,6 @@ xfs_iwalk_run_callbacks(
int *has_more) int *has_more)
{ {
struct xfs_mount *mp = iwag->mp; struct xfs_mount *mp = iwag->mp;
struct xfs_trans *tp = iwag->tp;
struct xfs_inobt_rec_incore *irec; struct xfs_inobt_rec_incore *irec;
xfs_agino_t next_agino; xfs_agino_t next_agino;
int error; int error;
...@@ -361,10 +363,15 @@ xfs_iwalk_run_callbacks( ...@@ -361,10 +363,15 @@ xfs_iwalk_run_callbacks(
ASSERT(iwag->nr_recs > 0); ASSERT(iwag->nr_recs > 0);
/* Delete cursor but remember the last record we cached... */ /* Delete cursor but remember the last record we cached... */
xfs_iwalk_del_inobt(tp, curpp, agi_bpp, 0); xfs_iwalk_del_inobt(iwag->tp, curpp, agi_bpp, 0);
irec = &iwag->recs[iwag->nr_recs - 1]; irec = &iwag->recs[iwag->nr_recs - 1];
ASSERT(next_agino == irec->ir_startino + XFS_INODES_PER_CHUNK); ASSERT(next_agino == irec->ir_startino + XFS_INODES_PER_CHUNK);
if (iwag->drop_trans) {
xfs_trans_cancel(iwag->tp);
iwag->tp = NULL;
}
error = xfs_iwalk_ag_recs(iwag); error = xfs_iwalk_ag_recs(iwag);
if (error) if (error)
return error; return error;
...@@ -375,8 +382,15 @@ xfs_iwalk_run_callbacks( ...@@ -375,8 +382,15 @@ xfs_iwalk_run_callbacks(
if (!has_more) if (!has_more)
return 0; return 0;
if (iwag->drop_trans) {
error = xfs_trans_alloc_empty(mp, &iwag->tp);
if (error)
return error;
}
/* ...and recreate the cursor just past where we left off. */ /* ...and recreate the cursor just past where we left off. */
error = xfs_inobt_cur(mp, tp, agno, XFS_BTNUM_INO, curpp, agi_bpp); error = xfs_inobt_cur(mp, iwag->tp, agno, XFS_BTNUM_INO, curpp,
agi_bpp);
if (error) if (error)
return error; return error;
...@@ -389,7 +403,6 @@ xfs_iwalk_ag( ...@@ -389,7 +403,6 @@ xfs_iwalk_ag(
struct xfs_iwalk_ag *iwag) struct xfs_iwalk_ag *iwag)
{ {
struct xfs_mount *mp = iwag->mp; struct xfs_mount *mp = iwag->mp;
struct xfs_trans *tp = iwag->tp;
struct xfs_buf *agi_bp = NULL; struct xfs_buf *agi_bp = NULL;
struct xfs_btree_cur *cur = NULL; struct xfs_btree_cur *cur = NULL;
xfs_agnumber_t agno; xfs_agnumber_t agno;
...@@ -469,7 +482,7 @@ xfs_iwalk_ag( ...@@ -469,7 +482,7 @@ xfs_iwalk_ag(
error = xfs_iwalk_run_callbacks(iwag, agno, &cur, &agi_bp, &has_more); error = xfs_iwalk_run_callbacks(iwag, agno, &cur, &agi_bp, &has_more);
out: out:
xfs_iwalk_del_inobt(tp, &cur, &agi_bp, error); xfs_iwalk_del_inobt(iwag->tp, &cur, &agi_bp, error);
return error; return error;
} }
...@@ -594,8 +607,18 @@ xfs_iwalk_ag_work( ...@@ -594,8 +607,18 @@ xfs_iwalk_ag_work(
error = xfs_iwalk_alloc(iwag); error = xfs_iwalk_alloc(iwag);
if (error) if (error)
goto out; goto out;
/*
* Grab an empty transaction so that we can use its recursive buffer
* locking abilities to detect cycles in the inobt without deadlocking.
*/
error = xfs_trans_alloc_empty(mp, &iwag->tp);
if (error)
goto out;
iwag->drop_trans = 1;
error = xfs_iwalk_ag(iwag); error = xfs_iwalk_ag(iwag);
if (iwag->tp)
xfs_trans_cancel(iwag->tp);
xfs_iwalk_free(iwag); xfs_iwalk_free(iwag);
out: out:
kmem_free(iwag); kmem_free(iwag);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册