* [PATCH] xfs: fix out-of-bounds write from crafted btree root numrecs during log recovery
@ 2026-10-27 15:17 Henry Martin
0 siblings, 0 replies; only message in thread
From: Henry Martin @ 2026-10-27 15:17 UTC (permalink / raw)
To: Carlos Maiolino, Andrew Morton, Darrick J . Wong, Alex Deucher,
David Hildenbrand, Kuninori Morimoto, Kairui Song, Myeonghun Pak,
Rafal Ostrowski, Likun Gao, James Clark, Anshuman Khandual,
Kevin Brodsky
Cc: linux-xfs, linux-kernel, Henry Martin, stable
xfs_bmbt_to_bmdr() converts a logged in-memory bmap btree root block
into its on-disk short form, and it trusts the source block's record
count for the copy sizes:
dblock->bb_level = rblock->bb_level;
dblock->bb_numrecs = rblock->bb_numrecs;
dmxr = xfs_bmdr_maxrecs(dblocklen, 0);
...
dmxr = be16_to_cpu(dblock->bb_numrecs);
memcpy(tkp, fkp, sizeof(*fkp) * dmxr);
memcpy(tpp, fpp, sizeof(*fpp) * dmxr);
The capacity bound (dmxr from xfs_bmdr_maxrecs) is computed and then
immediately overwritten with the attacker-controlled numrecs from the
source block. During log recovery, xlog_recover_inode_dbroot() and
the XFS_ILOG_DBROOT/XFS_ILOG_ABROOT paths pass a raw log iovec as the
source block, so a crafted log item with numrecs up to 0xffff makes the
two memcpy calls copy up to 512KB each (8 * 65535) out of the log
buffer and into the inode fork area (a few hundred bytes) and past the
end of the inode buffer page: a page-granularity out-of-bounds write
with fully attacker-controlled length and content, before any dinode
verification runs (xfs_dinode_verify() only sees the result
afterwards).
The same pattern exists in xfs_rtrmapbt_to_disk() and
xfs_rtrefcountbt_to_disk(), which convert rt rmap and refcount btree
roots from log blocks using the source numrecs unbounded.
Validate that the source numrecs fits within the destination capacity
before converting, in all three conversion helpers.
This issue was discovered by Tencent CodeBuddy Security.
Fixes: 658fa68b6f34 ("xfs: refactor log recovery inode item dispatch for pass2 commit functions")
Cc: stable@vger.kernel.org
Signed-off-by: Henry Martin <bsdhenrymartin@gmail.com>
---
fs/xfs/libxfs/xfs_bmap_btree.c | 13 ++++++++++++-
fs/xfs/libxfs/xfs_rtrefcount_btree.c | 13 +++++++++++++
fs/xfs/libxfs/xfs_rtrmap_btree.c | 12 ++++++++++++
3 files changed, 37 insertions(+), 1 deletion(-)
diff --git a/fs/xfs/libxfs/xfs_bmap_btree.c b/fs/xfs/libxfs/xfs_bmap_btree.c
index 758a4b1ccf5bb..118e8ab35f1d1 100644
--- a/fs/xfs/libxfs/xfs_bmap_btree.c
+++ b/fs/xfs/libxfs/xfs_bmap_btree.c
@@ -148,6 +148,7 @@ xfs_bmbt_to_bmdr(
xfs_bmdr_block_t *dblock,
int dblocklen)
{
+ int maxrecs;
int dmxr;
xfs_bmbt_key_t *fkp;
__be64 *fpp;
@@ -167,7 +168,17 @@ xfs_bmbt_to_bmdr(
ASSERT(rblock->bb_level != 0);
dblock->bb_level = rblock->bb_level;
dblock->bb_numrecs = rblock->bb_numrecs;
- dmxr = xfs_bmdr_maxrecs(dblocklen, 0);
+ maxrecs = xfs_bmdr_maxrecs(dblocklen, 0);
+ if (be16_to_cpu(dblock->bb_numrecs) > maxrecs) {
+ /* Corrupted log block: refuse the conversion instead of
+ * copying past the end of the inode fork area.
+ */
+ xfs_warn(mp, "bmbt log recovery: numrecs %d > max %d",
+ be16_to_cpu(dblock->bb_numrecs), maxrecs);
+ memset(dblock, 0, dblocklen);
+ return;
+ }
+ dmxr = maxrecs;
fkp = xfs_bmbt_key_addr(mp, rblock, 1);
tkp = xfs_bmdr_key_addr(dblock, 1);
fpp = xfs_bmap_broot_ptr_addr(mp, rblock, 1, rblocklen);
diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c
index dcc89b8e149b0..c6b351b7fbcdb 100644
--- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c
+++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c
@@ -696,6 +696,19 @@ xfs_rtrefcountbt_to_disk(
dblock->bb_level = rblock->bb_level;
dblock->bb_numrecs = rblock->bb_numrecs;
+ /* Corrupted log block: refuse the conversion instead of copying
+ * past the end of the inode fork area.
+ */
+ numrecs = be16_to_cpu(rblock->bb_numrecs);
+ maxrecs = xfs_rtrefcountbt_droot_maxrecs(dblocklen,
+ be16_to_cpu(rblock->bb_level) == 0);
+ if (numrecs > maxrecs) {
+ xfs_warn(mp, "rtrefcountbt log recovery: numrecs %u > max %u",
+ numrecs, maxrecs);
+ memset(dblock, 0, dblocklen);
+ return;
+ }
+
if (be16_to_cpu(rblock->bb_level) > 0) {
maxrecs = xfs_rtrefcountbt_droot_maxrecs(dblocklen, false);
fkp = xfs_rtrefcount_key_addr(rblock, 1);
diff --git a/fs/xfs/libxfs/xfs_rtrmap_btree.c b/fs/xfs/libxfs/xfs_rtrmap_btree.c
index a15e460a1ec7f..29d948b42af66 100644
--- a/fs/xfs/libxfs/xfs_rtrmap_btree.c
+++ b/fs/xfs/libxfs/xfs_rtrmap_btree.c
@@ -929,6 +929,18 @@ xfs_rtrmapbt_to_disk(
dblock->bb_numrecs = rblock->bb_numrecs;
numrecs = be16_to_cpu(rblock->bb_numrecs);
+ /* Corrupted log block: refuse the conversion instead of copying
+ * past the end of the inode fork area.
+ */
+ maxrecs = xfs_rtrmapbt_droot_maxrecs(dblocklen,
+ be16_to_cpu(rblock->bb_level) == 0);
+ if (numrecs > maxrecs) {
+ xfs_warn(mp, "rtrmapbt log recovery: numrecs %u > max %u",
+ numrecs, maxrecs);
+ memset(dblock, 0, dblocklen);
+ return;
+ }
+
if (be16_to_cpu(rblock->bb_level) > 0) {
maxrecs = xfs_rtrmapbt_droot_maxrecs(dblocklen, false);
fkp = xfs_rtrmap_key_addr(rblock, 1);
--
2.43.7
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-27 15:17 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-27 15:17 [PATCH] xfs: fix out-of-bounds write from crafted btree root numrecs during log recovery Henry Martin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®