From nobody Sat Jul 25 03:46:04 2026 Received: from mail-pl1-f180.google.com (mail-pl1-f180.google.com [209.85.214.180]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 08DB439023D for ; Sun, 19 Jul 2026 11:30:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784460631; cv=none; b=GX1A2KoCznB+zZsBaNZ4Pathkdd6d2ZS8h+QnPgqQlg/sfVpLkO5FYl+Hdj6XbtQ9ATGgXnmFuZySC09yeYMmi86B4rtDS6spxRv8q/K9QHTQKMrNc5l2BKy7bD8ZJnD5FLhQxtfTsTHZANzrdT77n3Jh1NE5kPrZksiSciFSuI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784460631; c=relaxed/simple; bh=FSXmXuZkpD9nyi/dqQkJZh9+x96eMR1gGqIF9ULr7Po=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=c7nkQMJ998Uv4bktFnGHoJgDdWsefY1fkr6sfTxMUw+u0vpU1MRz3K1tHp413OsE8Uqxg+zwt1V6F1OpQVsK2jbm4O3DGHTWscq8dyyu2brYdRWP9CckJkKkw98NnQZz7U/d2O5VnWUiIHV+8CFgYMroM6HgdHJh50Se7NWQXn4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=N5MZwzt7; arc=none smtp.client-ip=209.85.214.180 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="N5MZwzt7" Received: by mail-pl1-f180.google.com with SMTP id d9443c01a7336-2cc7e86e7aeso63688645ad.2 for ; Sun, 19 Jul 2026 04:30:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784460624; x=1785065424; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=1Afy7GemuDlWgnkzFmnf+fPudhFvneCa90qqK9FLigo=; b=N5MZwzt7TV/DZrK6WQoqpLn2UITpvF7TZdMe5zBVAKaORi1Qz18gf08I0d5M3GwUTJ tlzz8dYFPaDkEURLWeUIpXa51qxUfeD8Y8tzXyVKrFDLFyk8GglBh4HN6poiAfYrDGU/ ApcLEDCgl2D8F+TAiKsypmzFjF/Pb3LEt3ClE0erMHEwZejjUnOd+Z4h/ZYCzeNoJ6Q7 ux52NCTI3C/gWnFzCVMPSddN6hDckXas4PGASXKfcVH/rsz6cilZ+m4+rc3a5ZdmYZ3Y DqgqmOnIJG1/qZeshLgZbeJz3mt9LCzhyBdyu0PXjiJMmPpjSPfKXza2CtP1GMpVhAbo f5aA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784460624; x=1785065424; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=1Afy7GemuDlWgnkzFmnf+fPudhFvneCa90qqK9FLigo=; b=UDWO74iB/kK72egm/U+xXXfgMfQFHTHFpt+9UwQbjKeleFqB4f041sz1qSFSQOtqzC t4X96NCBfri1ImHIUB33BmnGhz0ITW6DkQaZ6a7Rq5LGI0TF8rdoirLoHiCTbY865b6V aG8q5REG15wKqVrE9IXoUJVU95Zu71hdD375ozlubiTIDtDI9xpgkPvoNnFD6lPsG4xm L0qPwbuAIAaPOJmbibg+2eRT0dBVJrkV2E9SKhL1snbKE5gdqpsK8DPtZw0Io0P/E45J UPxT6G4tgvNm9WNyb4tOgnsmMwuuPTcT9sxIkZo2Oj3xbKGlhdAZN8jLsRIl1Z/QCF6D pfHA== X-Forwarded-Encrypted: i=1; AHgh+Rr5ihV2mxf/rktmnowTbFG27h7yeyVFQ39plgLgCQHfOn9DhEW1aPHl9n3UftZm5zkIPf2P4AhTkIK23lo=@vger.kernel.org X-Gm-Message-State: AOJu0Yz/KC6HJE/8PZfmiIB+nuYHgoNQ4yGY5k3oZdZummv2oxgzfk05 FH9cglCEqSWl/7td8Lb7oeUFwceFFLSQYdZnQJS9zqw8oRAW3Ole+2Duyzs/MYmBjGY= X-Gm-Gg: AfdE7cmXkp8j8uLJJp6uG2oB1kx6w8Am0qOSQChKH6+Oj5klrUtjUvaKOwtxKLjrfKa EGqNjSt/3BBMONbe9UNEYRwBIkT8jkmY4EEi1849V6VudPvY0aj2Xt/YAOZEAXHxJA6UZVQj53d GmCH3QeLRrRGTTa8+lqqOYejuJF5txw0zsYti/2za1JDocgCv08DT0o2bvCPEDAq1gNkdWoOzNr HsuHg49zGZg88G8Sav/WzGhnGzd+y9AleIux2rYNgbYrjtX3+zo2TSh9JvyOqVgfH0jvTzTiQa1 eWYyMPTsSIm7Y4IjVWhmhzsgaXrexSlRgOK3Md2KLIe9pguHAgKBA/i7B1XJD1u6qn1yetcOc9e l7y9t54tzQ5/+BevTUVrMPAzBeD8YB6s3hCigir6rweHjcBelNUWXwjMt3xyBMTJklfMB6+yLxI sV+EcLpk93qjJUfxYPy0vHf2lPj37bNtjh5JgIXIVrAPvFDuXpQAhG7uGsVA== X-Received: by 2002:a17:903:178e:b0:2c9:e9c7:2b59 with SMTP id d9443c01a7336-2cf349cc765mr105343195ad.35.1784460624277; Sun, 19 Jul 2026 04:30:24 -0700 (PDT) Received: from fx.tailc0aff1.ts.net ([206.206.192.132]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13ce2ddfb35sm22035031c88.14.2026.07.19.04.30.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 19 Jul 2026 04:30:23 -0700 (PDT) From: Weiming Shi To: Carlos Maiolino , "Darrick J . Wong" Cc: linux-xfs@vger.kernel.org, linux-kernel@vger.kernel.org, xmei5@asu.edu, Weiming Shi , Dave Chinner Subject: [PATCH v3 1/3] xfs: verify log item headers when they are decoded during recovery Date: Sun, 19 Jul 2026 04:29:21 -0700 Message-ID: <20260719112923.226550-2-bestswngs@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260719112923.226550-1-bestswngs@gmail.com> References: <20260719112923.226550-1-bestswngs@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Log recovery rebuilds each log item in xlog_recover_add_to_trans() from the ophdr regions in the journal. The first region carries the item's format header, and recovery reads the type from its first two bytes and the region count from the next two to size and populate the item's ri_buf[] array. Those regions are later cast back to their format structures and used to drive replay. This is the first point at which recovery trusts data read from the log, and the geometry it reads here is used to build the structures every later check and decode step relies on, so the header should be verified comprehensively here rather than trusted until something dereferences it. Do that in xlog_recover_verify_item_header(): require the region to be large enough to hold the type and count fields before they are read, resolve the item type against the known xlog_recover_item_ops (rejecting unrecognised types), and bound the declared region count within the minimum and maximum a log item of that type is formatted with. A lower bound matters as much as an upper one: an item that declares fewer regions than its replay code indexes still passes the completeness check added later in the series and then reads past its ri_buf[] array, e.g. a dquot item with qlf_size =3D=3D 1 still reaches ri_buf[1] in xlog_recover_dquot_commit_pass2(). Record both bounds in new min_regions and max_regions fields on xlog_recover_item_ops, where a max_regions of 0 keeps the generic XLOG_MAX_REGIONS_IN_ITEM ceiling used by buffer items. Resolving the type here also lets us set item->ri_ops at decode time, so xlog_recover_reorder_trans() no longer has to look it up and can no longer meet an unrecognised item. While decoding the continuation of a region in xlog_recover_add_to_cont_trans(), also reject a continuation that has no started region to extend, instead of indexing ri_buf[-1] on a never-populated item. Suggested-by: Dave Chinner Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Weiming Shi --- fs/xfs/libxfs/xfs_log_recover.h | 8 ++++ fs/xfs/xfs_attr_item.c | 4 ++ fs/xfs/xfs_bmap_item.c | 4 ++ fs/xfs/xfs_dquot_item_recover.c | 4 ++ fs/xfs/xfs_exchmaps_item.c | 4 ++ fs/xfs/xfs_extfree_item.c | 8 ++++ fs/xfs/xfs_icreate_item.c | 2 + fs/xfs/xfs_inode_item_recover.c | 2 + fs/xfs/xfs_log_recover.c | 80 ++++++++++++++++++++------------- fs/xfs/xfs_refcount_item.c | 8 ++++ fs/xfs/xfs_rmap_item.c | 8 ++++ 11 files changed, 101 insertions(+), 31 deletions(-) diff --git a/fs/xfs/libxfs/xfs_log_recover.h b/fs/xfs/libxfs/xfs_log_recove= r.h index 9e712e62369c..77f51afcbd9c 100644 --- a/fs/xfs/libxfs/xfs_log_recover.h +++ b/fs/xfs/libxfs/xfs_log_recover.h @@ -24,6 +24,14 @@ enum xlog_recover_reorder { struct xlog_recover_item_ops { uint16_t item_type; /* XFS_LI_* type code. */ =20 + /* + * Bounds on the item's declared region count, checked before the + * region array is allocated. max_regions 0 means no fixed maximum, so + * the generic XLOG_MAX_REGIONS_IN_ITEM ceiling applies (buffers). + */ + unsigned int min_regions; + unsigned int max_regions; + /* * Help sort recovered log items into the order required to replay them * correctly. Log item types that always use XLOG_REORDER_ITEM_LIST do diff --git a/fs/xfs/xfs_attr_item.c b/fs/xfs/xfs_attr_item.c index a53ee2b0c54a..cec7c1bb3694 100644 --- a/fs/xfs/xfs_attr_item.c +++ b/fs/xfs/xfs_attr_item.c @@ -1189,6 +1189,8 @@ static const struct xfs_item_ops xfs_attri_item_ops = =3D { =20 const struct xlog_recover_item_ops xlog_attri_item_ops =3D { .item_type =3D XFS_LI_ATTRI, + .max_regions =3D 5, + .min_regions =3D 2, .commit_pass2 =3D xlog_recover_attri_commit_pass2, }; =20 @@ -1203,5 +1205,7 @@ static const struct xfs_item_ops xfs_attrd_item_ops = =3D { =20 const struct xlog_recover_item_ops xlog_attrd_item_ops =3D { .item_type =3D XFS_LI_ATTRD, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_attrd_commit_pass2, }; diff --git a/fs/xfs/xfs_bmap_item.c b/fs/xfs/xfs_bmap_item.c index 89f6e79a955f..8659428a51bb 100644 --- a/fs/xfs/xfs_bmap_item.c +++ b/fs/xfs/xfs_bmap_item.c @@ -684,6 +684,8 @@ xlog_recover_bui_commit_pass2( =20 const struct xlog_recover_item_ops xlog_bui_item_ops =3D { .item_type =3D XFS_LI_BUI, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_bui_commit_pass2, }; =20 @@ -716,5 +718,7 @@ xlog_recover_bud_commit_pass2( =20 const struct xlog_recover_item_ops xlog_bud_item_ops =3D { .item_type =3D XFS_LI_BUD, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_bud_commit_pass2, }; diff --git a/fs/xfs/xfs_dquot_item_recover.c b/fs/xfs/xfs_dquot_item_recove= r.c index fe419b28de22..5882c688ecc1 100644 --- a/fs/xfs/xfs_dquot_item_recover.c +++ b/fs/xfs/xfs_dquot_item_recover.c @@ -178,6 +178,8 @@ xlog_recover_dquot_commit_pass2( =20 const struct xlog_recover_item_ops xlog_dquot_item_ops =3D { .item_type =3D XFS_LI_DQUOT, + .max_regions =3D 2, + .min_regions =3D 2, .ra_pass2 =3D xlog_recover_dquot_ra_pass2, .commit_pass2 =3D xlog_recover_dquot_commit_pass2, }; @@ -211,6 +213,8 @@ xlog_recover_quotaoff_commit_pass1( =20 const struct xlog_recover_item_ops xlog_quotaoff_item_ops =3D { .item_type =3D XFS_LI_QUOTAOFF, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass1 =3D xlog_recover_quotaoff_commit_pass1, /* nothing to commit in pass2 */ }; diff --git a/fs/xfs/xfs_exchmaps_item.c b/fs/xfs/xfs_exchmaps_item.c index c3745d33e54e..d3f0fb677341 100644 --- a/fs/xfs/xfs_exchmaps_item.c +++ b/fs/xfs/xfs_exchmaps_item.c @@ -576,6 +576,8 @@ xlog_recover_xmi_commit_pass2( =20 const struct xlog_recover_item_ops xlog_xmi_item_ops =3D { .item_type =3D XFS_LI_XMI, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_xmi_commit_pass2, }; =20 @@ -607,5 +609,7 @@ xlog_recover_xmd_commit_pass2( =20 const struct xlog_recover_item_ops xlog_xmd_item_ops =3D { .item_type =3D XFS_LI_XMD, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_xmd_commit_pass2, }; diff --git a/fs/xfs/xfs_extfree_item.c b/fs/xfs/xfs_extfree_item.c index 2266d56e37dc..dccc3d159268 100644 --- a/fs/xfs/xfs_extfree_item.c +++ b/fs/xfs/xfs_extfree_item.c @@ -889,6 +889,8 @@ xlog_recover_efi_commit_pass2( =20 const struct xlog_recover_item_ops xlog_efi_item_ops =3D { .item_type =3D XFS_LI_EFI, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_efi_commit_pass2, }; =20 @@ -941,6 +943,8 @@ xlog_recover_rtefi_commit_pass2( =20 const struct xlog_recover_item_ops xlog_rtefi_item_ops =3D { .item_type =3D XFS_LI_EFI_RT, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_rtefi_commit_pass2, }; =20 @@ -984,6 +988,8 @@ xlog_recover_efd_commit_pass2( =20 const struct xlog_recover_item_ops xlog_efd_item_ops =3D { .item_type =3D XFS_LI_EFD, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_efd_commit_pass2, }; =20 @@ -1025,5 +1031,7 @@ xlog_recover_rtefd_commit_pass2( =20 const struct xlog_recover_item_ops xlog_rtefd_item_ops =3D { .item_type =3D XFS_LI_EFD_RT, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_rtefd_commit_pass2, }; diff --git a/fs/xfs/xfs_icreate_item.c b/fs/xfs/xfs_icreate_item.c index 95b0eba242e9..9afe58f7bfd4 100644 --- a/fs/xfs/xfs_icreate_item.c +++ b/fs/xfs/xfs_icreate_item.c @@ -255,6 +255,8 @@ xlog_recover_icreate_commit_pass2( =20 const struct xlog_recover_item_ops xlog_icreate_item_ops =3D { .item_type =3D XFS_LI_ICREATE, + .max_regions =3D 1, + .min_regions =3D 1, .reorder =3D xlog_recover_icreate_reorder, .commit_pass2 =3D xlog_recover_icreate_commit_pass2, }; diff --git a/fs/xfs/xfs_inode_item_recover.c b/fs/xfs/xfs_inode_item_recove= r.c index 169a8fe3bf0a..6b6ac92964d0 100644 --- a/fs/xfs/xfs_inode_item_recover.c +++ b/fs/xfs/xfs_inode_item_recover.c @@ -599,6 +599,8 @@ xlog_recover_inode_commit_pass2( =20 const struct xlog_recover_item_ops xlog_inode_item_ops =3D { .item_type =3D XFS_LI_INODE, + .max_regions =3D 4, + .min_regions =3D 2, .ra_pass2 =3D xlog_recover_inode_ra_pass2, .commit_pass2 =3D xlog_recover_inode_commit_pass2, }; diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c index 09e6678ca487..41fa029054d3 100644 --- a/fs/xfs/xfs_log_recover.c +++ b/fs/xfs/xfs_log_recover.c @@ -1828,12 +1828,12 @@ static const struct xlog_recover_item_ops *xlog_rec= over_item_ops[] =3D { =20 static const struct xlog_recover_item_ops * xlog_find_item_ops( - struct xlog_recover_item *item) + unsigned short item_type) { unsigned int i; =20 for (i =3D 0; i < ARRAY_SIZE(xlog_recover_item_ops); i++) - if (ITEM_TYPE(item) =3D=3D xlog_recover_item_ops[i]->item_type) + if (item_type =3D=3D xlog_recover_item_ops[i]->item_type) return xlog_recover_item_ops[i]; =20 return NULL; @@ -1888,14 +1888,13 @@ xlog_find_item_ops( * but for all other items there may be specific ordering that we need to * preserve. */ -STATIC int +STATIC void xlog_recover_reorder_trans( struct xlog *log, struct xlog_recover *trans, int pass) { struct xlog_recover_item *item, *n; - int error =3D 0; LIST_HEAD(sort_list); LIST_HEAD(cancel_list); LIST_HEAD(buffer_list); @@ -1906,22 +1905,6 @@ xlog_recover_reorder_trans( list_for_each_entry_safe(item, n, &sort_list, ri_list) { enum xlog_recover_reorder fate =3D XLOG_REORDER_ITEM_LIST; =20 - item->ri_ops =3D xlog_find_item_ops(item); - if (!item->ri_ops) { - xfs_warn(log->l_mp, - "%s: unrecognized type of log operation (%d)", - __func__, ITEM_TYPE(item)); - ASSERT(0); - /* - * return the remaining items back to the transaction - * item list so they can be freed in caller. - */ - if (!list_empty(&sort_list)) - list_splice_init(&sort_list, &trans->r_itemq); - error =3D -EFSCORRUPTED; - break; - } - if (item->ri_ops->reorder) fate =3D item->ri_ops->reorder(item); =20 @@ -1954,7 +1937,6 @@ xlog_recover_reorder_trans( list_splice_tail(&inode_buffer_list, &trans->r_itemq); if (!list_empty(&cancel_list)) list_splice_tail(&cancel_list, &trans->r_itemq); - return error; } =20 void @@ -2039,9 +2021,7 @@ xlog_recover_commit_trans( =20 hlist_del_init(&trans->r_list); =20 - error =3D xlog_recover_reorder_trans(log, trans, pass); - if (error) - return error; + xlog_recover_reorder_trans(log, trans, pass); =20 list_for_each_entry_safe(item, next, &trans->r_itemq, ri_list) { trace_xfs_log_recover_item_recover(log, trans, item, pass); @@ -2130,6 +2110,10 @@ xlog_recover_add_to_cont_trans( item =3D list_entry(trans->r_itemq.prev, struct xlog_recover_item, ri_list); =20 + /* the continuation has to extend a region we have already started */ + if (XFS_IS_CORRUPT(log->l_mp, item->ri_cnt =3D=3D 0 || !item->ri_buf)) + return -EFSCORRUPTED; + old_ptr =3D item->ri_buf[item->ri_cnt-1].iov_base; old_len =3D item->ri_buf[item->ri_cnt-1].iov_len; =20 @@ -2143,6 +2127,43 @@ xlog_recover_add_to_cont_trans( return 0; } =20 +/* + * Validate a newly decoded item header before recovery trusts it to size = and + * assemble the item's regions: resolve the item type and bound the declar= ed + * region count. + */ +STATIC int +xlog_recover_verify_item_header( + struct xlog *log, + struct xlog_recover_item *item, + char *ptr, + int len) +{ + struct xfs_inode_log_format *in_f =3D (struct xfs_inode_log_format *)ptr; + const struct xlog_recover_item_ops *ops; + unsigned int max_regions; + + /* The type and region count fields have to be present to be read. */ + if (XFS_IS_CORRUPT(log->l_mp, + len < offsetofend(struct xfs_inode_log_format, ilf_size))) + return -EFSCORRUPTED; + + ops =3D xlog_find_item_ops(in_f->ilf_type); + if (XFS_IS_CORRUPT(log->l_mp, !ops)) + return -EFSCORRUPTED; + item->ri_ops =3D ops; + + max_regions =3D ops->max_regions ? ops->max_regions : + XLOG_MAX_REGIONS_IN_ITEM; + if (XFS_IS_CORRUPT(log->l_mp, + in_f->ilf_size =3D=3D 0 || + in_f->ilf_size < ops->min_regions || + in_f->ilf_size > max_regions)) + return -EFSCORRUPTED; + + return 0; +} + /* * The next region to add is the start of a new region. It could be * a whole region or it could be the first part of a new region. Because @@ -2166,6 +2187,7 @@ xlog_recover_add_to_trans( struct xfs_inode_log_format *in_f; /* any will do */ struct xlog_recover_item *item; char *ptr; + int error; =20 if (!len) return 0; @@ -2211,14 +2233,10 @@ xlog_recover_add_to_trans( } =20 if (item->ri_total =3D=3D 0) { /* first region to be added */ - if (in_f->ilf_size =3D=3D 0 || - in_f->ilf_size > XLOG_MAX_REGIONS_IN_ITEM) { - xfs_warn(log->l_mp, - "bad number of regions (%d) in inode log format", - in_f->ilf_size); - ASSERT(0); + error =3D xlog_recover_verify_item_header(log, item, ptr, len); + if (error) { kvfree(ptr); - return -EFSCORRUPTED; + return error; } =20 item->ri_total =3D in_f->ilf_size; diff --git a/fs/xfs/xfs_refcount_item.c b/fs/xfs/xfs_refcount_item.c index 8bccf89a7766..ecd2be4d425b 100644 --- a/fs/xfs/xfs_refcount_item.c +++ b/fs/xfs/xfs_refcount_item.c @@ -741,6 +741,8 @@ xlog_recover_cui_commit_pass2( =20 const struct xlog_recover_item_ops xlog_cui_item_ops =3D { .item_type =3D XFS_LI_CUI, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_cui_commit_pass2, }; =20 @@ -796,6 +798,8 @@ xlog_recover_rtcui_commit_pass2( =20 const struct xlog_recover_item_ops xlog_rtcui_item_ops =3D { .item_type =3D XFS_LI_CUI_RT, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_rtcui_commit_pass2, }; =20 @@ -828,6 +832,8 @@ xlog_recover_cud_commit_pass2( =20 const struct xlog_recover_item_ops xlog_cud_item_ops =3D { .item_type =3D XFS_LI_CUD, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_cud_commit_pass2, }; =20 @@ -858,5 +864,7 @@ xlog_recover_rtcud_commit_pass2( =20 const struct xlog_recover_item_ops xlog_rtcud_item_ops =3D { .item_type =3D XFS_LI_CUD_RT, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_rtcud_commit_pass2, }; diff --git a/fs/xfs/xfs_rmap_item.c b/fs/xfs/xfs_rmap_item.c index 2a3a73a8566d..201c8cea74f5 100644 --- a/fs/xfs/xfs_rmap_item.c +++ b/fs/xfs/xfs_rmap_item.c @@ -770,6 +770,8 @@ xlog_recover_rui_commit_pass2( =20 const struct xlog_recover_item_ops xlog_rui_item_ops =3D { .item_type =3D XFS_LI_RUI, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_rui_commit_pass2, }; =20 @@ -825,6 +827,8 @@ xlog_recover_rtrui_commit_pass2( =20 const struct xlog_recover_item_ops xlog_rtrui_item_ops =3D { .item_type =3D XFS_LI_RUI_RT, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_rtrui_commit_pass2, }; =20 @@ -857,6 +861,8 @@ xlog_recover_rud_commit_pass2( =20 const struct xlog_recover_item_ops xlog_rud_item_ops =3D { .item_type =3D XFS_LI_RUD, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_rud_commit_pass2, }; =20 @@ -887,5 +893,7 @@ xlog_recover_rtrud_commit_pass2( =20 const struct xlog_recover_item_ops xlog_rtrud_item_ops =3D { .item_type =3D XFS_LI_RUD_RT, + .max_regions =3D 1, + .min_regions =3D 1, .commit_pass2 =3D xlog_recover_rtrud_commit_pass2, }; --=20 2.43.0 From nobody Sat Jul 25 03:46:04 2026 Received: from mail-pl1-f169.google.com (mail-pl1-f169.google.com [209.85.214.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D827B3911BC for ; Sun, 19 Jul 2026 11:30:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784460631; cv=none; b=UkzhZ67vpdGDrUwcXkX5n5yTW67IrRXm/FyakzkRlFtBrDIF8EKRLyBheR9SyttmL2VETEsqQ8W1DJywGmgE7T6TqwMZrXy4Rbs7Xya392GWDUgga1kBM0LyFEp7d4hyC1xI968Wq2bSRVQR/XbgJkUzc09WN+gPzWaZUMU0e9k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784460631; c=relaxed/simple; bh=V1E4ldfQJDyP6ZfDcqT6Ru436JirUZfMBpHQx0g4yjI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=EgLTNhmC04kct85PLMQdM+ArXKxm28Ywa+ILOGevQhn9dvL2rKeuxp4lEAUpaqwYbO7Kakx+4T3JsBgEtSMRDlC1+pWHsaCjDUDPyiFBmHDblG26jUpogi0OqvpyGElu7CkteibYhUAfpeT7TBX6PhKCCFzc8XbP/VA4sGk4Li8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=VmEJeIzQ; arc=none smtp.client-ip=209.85.214.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="VmEJeIzQ" Received: by mail-pl1-f169.google.com with SMTP id d9443c01a7336-2cc97653887so102034175ad.1 for ; Sun, 19 Jul 2026 04:30:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784460626; x=1785065426; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=N18ILb2wHDXeZG6Bb9nkkO9XUrYEdry3ahf/Z0caz74=; b=VmEJeIzQJ77jAmOi29pP3HFG6nELdj+RZ2ej5xk0arn0XwzJvasZ75SpV+KCjXGL+j 2LtdY1Zz3exH+50LEkHMhx4DWL6AkHFoYeCSju0W0XTkQiOu8AaYo1rZIUb5BsS9CyS9 7+sn4EIDmyXJmA8bul4ZRBNMrrAkG2TTFtgAjd4JvnmmzYK8o2uH6CyNr60fpEraioKW YzroAwsyzZnnINYPUlGgTMIoNzcstIqwvwGig8Y6Twd1QHUewhPqLif+R7bYNg2cwiNz S77XNhHuUJJNT8dSXIaWGEGFusyKt4rOy3WzH6mMhOImg7/m4dMfzh2yE3CeqwDWamFc 2nRg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784460626; x=1785065426; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=N18ILb2wHDXeZG6Bb9nkkO9XUrYEdry3ahf/Z0caz74=; b=BrKRWUabB6IyCuILD3zExartHwy9giqomxmQe1k0xZ9b3O+fEpj41uX38HMwOMCuLE mV/yBuec/5OWyeqXrbOY2AeEA+xoBCWYCk+8mxee1jtmcsLoL71k2skJZY9pstkS4QJ5 1H162ASlTt3K7yXlp88rgMwv+ejO3LLZMiEJw46etdfrQK8+F78AJC/GjNjzMhdB+vdp ybMuCplldHTwQmXlL7HFdh/+Wr/USK80WTtWI8zcxQaftlckQW8YHIEUoWHi5mhG7iPE iaX/u2gbrnwEurnNVmq3FNsOdJhpUEkrnxNugzrpJAi5mspvvhPEIgJMqTirKS3T9fkk v2mA== X-Forwarded-Encrypted: i=1; AHgh+Rrc97V7ZLR+uzXHfJGlBraQA3HtnFvPz5J2SGMzQ4ADhXz8LF47+f/fGLgbdKt4x7XdTRunzkQmIm8hKus=@vger.kernel.org X-Gm-Message-State: AOJu0Yx8mAwFjJ5oiScyYtZDTeRzdYl1C+h6+39T8AynyhSUMS/HHl2S k8inJTvN61quFRQmisNnoZBsd4uMo5atjy0/zpROjUR3V1Eu2KE7kAOD X-Gm-Gg: AfdE7cnN71Uz+Dj8q4J7qDA1QyHJCI84/ydrYb2vcsJwysTg1FXHoWk6XyFzqwNUePf JsEdI9jBCKtGk9+Zr5xuF+ie5FKuwgrGm2eOZtSDnBFBIb91x8MPZzYOhyecWDQ2m53RFzzvvv0 FbcA3g/dR7obpKflD9OeYl07ypNX/OkzcFnJbmFsmHv5QICllbagcPaoy/McoS0fKtVGJlHbs+5 nUa7ieEfghtSmnbCT3z5WCYrv+93PIRSnCvJjBBApOyUTW3xzukO4R/DwN7ocDwfertSkSxcXdM m9EuN+We9OaGKLi68FvlcpOWkgHQT8gox803fVIy392QaO5kCCIAFjJmBTNP8mSRBA5LNXph9ut OITpuIhoqT5xosvF3NNrUy8JSBhLC9NJaiDdU4H89RRlQ/Ea2Aiw4HtoDkdQS16fOyI2JAZHg92 rnO3PeajiedOs57f+tkSx8dienCI9YhnvZ1TbkZWCSuCk4dNp2O60DbLyl2g== X-Received: by 2002:a17:902:ccc8:b0:2c9:ae0b:61e3 with SMTP id d9443c01a7336-2cf3484b193mr90357755ad.2.1784460625780; Sun, 19 Jul 2026 04:30:25 -0700 (PDT) Received: from fx.tailc0aff1.ts.net ([206.206.192.132]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13ce2ddfb35sm22035031c88.14.2026.07.19.04.30.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 19 Jul 2026 04:30:24 -0700 (PDT) From: Weiming Shi To: Carlos Maiolino , "Darrick J . Wong" Cc: linux-xfs@vger.kernel.org, linux-kernel@vger.kernel.org, xmei5@asu.edu, Weiming Shi , Dave Chinner Subject: [PATCH v3 2/3] xfs: verify recovered log items are complete before replaying them Date: Sun, 19 Jul 2026 04:29:22 -0700 Message-ID: <20260719112923.226550-3-bestswngs@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260719112923.226550-1-bestswngs@gmail.com> References: <20260719112923.226550-1-bestswngs@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" xlog_recover_add_to_trans() assembles each recovered log item into an ri_buf[] of ri_total slots, where ri_total is the region count the item's format header declared, and counts the regions actually logged in ri_cnt. Nothing checked that ri_cnt reached ri_total once the item was fully decoded, so a crafted or truncated log can present an item whose header declares more regions than were logged. The trailing ri_buf[] slots stay NULL, and the reorder, readahead and replay code then dereference them. For example, an XFS_LI_INODE item declaring two regions but logging only the format region leaves ri_buf[1] NULL, and mount-time recovery faults dereferencing it as the log dinode: KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: xlog_recover_inode_commit_pass2 (fs/xfs/xfs_inode_item_recover.c:370) Call Trace: xlog_recover_items_pass2 (fs/xfs/xfs_log_recover.c:2011) xlog_recover_commit_trans (fs/xfs/xfs_log_recover.c:2078) xlog_recovery_process_trans (fs/xfs/xfs_log_recover.c:2328) xlog_recover_process_data (fs/xfs/xfs_log_recover.c:2502) xlog_recover (fs/xfs/xfs_log_recover.c:3486) xfs_log_mount (fs/xfs/xfs_log.c:667) xfs_mountfs (fs/xfs/xfs_mount.c:1039) xfs_fs_fill_super (fs/xfs/xfs_super.c:1965) get_tree_bdev_flags (fs/super.c:1680) __x64_sys_mount (fs/namespace.c:4433) An item is fully decoded once every region its header declared has arrived. That happens when the next item's header starts (the current item is closed out in xlog_recover_add_to_trans()) or, for the last item in the transaction, when the commit record arrives (xlog_recover_commit_trans()). Verify the item at both of those points with xlog_recover_verify_item(): confirm it received all its declared regions, and run the item type's verifier if one is provided. Item types opt in with a new xlog_recover_item_ops->verify method; the first, for inode items, is added in the next patch. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Xiang Mei Suggested-by: Dave Chinner Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Weiming Shi --- fs/xfs/libxfs/xfs_log_recover.h | 9 ++++++++ fs/xfs/xfs_log_recover.c | 41 ++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_log_recover.h b/fs/xfs/libxfs/xfs_log_recove= r.h index 77f51afcbd9c..c7c93c65f60a 100644 --- a/fs/xfs/libxfs/xfs_log_recover.h +++ b/fs/xfs/libxfs/xfs_log_recover.h @@ -41,6 +41,15 @@ struct xlog_recover_item_ops { */ enum xlog_recover_reorder (*reorder)(struct xlog_recover_item *item); =20 + /* + * Comprehensively validate a fully decoded item's formatted log + * structures, if provided: the region count and sizes the item type + * requires, and any header fields that can be checked without the + * on-disk buffer. Called once the item is complete, before it is + * queued for replay. Returning an error aborts recovery. + */ + int (*verify)(struct xlog *log, struct xlog_recover_item *item); + /* Start readahead for pass2, if provided. */ void (*ra_pass2)(struct xlog *log, struct xlog_recover_item *item); =20 diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c index 41fa029054d3..956599b73b16 100644 --- a/fs/xfs/xfs_log_recover.c +++ b/fs/xfs/xfs_log_recover.c @@ -1997,6 +1997,9 @@ xlog_recover_items_pass2( return error; } =20 +STATIC int xlog_recover_verify_item(struct xlog *log, + struct xlog_recover_item *item); + /* * Perform the transaction. * @@ -2021,6 +2024,18 @@ xlog_recover_commit_trans( =20 hlist_del_init(&trans->r_list); =20 + /* + * The final item is completed by the commit record rather than by a + * following item, so no decode step has verified it yet; do so now. + */ + if (!list_empty(&trans->r_itemq)) { + item =3D list_entry(trans->r_itemq.prev, + struct xlog_recover_item, ri_list); + error =3D xlog_recover_verify_item(log, item); + if (error) + return error; + } + xlog_recover_reorder_trans(log, trans, pass); =20 list_for_each_entry_safe(item, next, &trans->r_itemq, ri_list) { @@ -2164,6 +2179,25 @@ xlog_recover_verify_item_header( return 0; } =20 +/* + * A fully decoded item has received all its declared regions. Check it is + * complete and run the type's verifier, if any, before it is queued for + * replay. + */ +STATIC int +xlog_recover_verify_item( + struct xlog *log, + struct xlog_recover_item *item) +{ + if (XFS_IS_CORRUPT(log->l_mp, + item->ri_total =3D=3D 0 || item->ri_cnt !=3D item->ri_total)) + return -EFSCORRUPTED; + + if (item->ri_ops->verify) + return item->ri_ops->verify(log, item); + return 0; +} + /* * The next region to add is the start of a new region. It could be * a whole region or it could be the first part of a new region. Because @@ -2226,7 +2260,12 @@ xlog_recover_add_to_trans( ri_list); if (item->ri_total !=3D 0 && item->ri_total =3D=3D item->ri_cnt) { - /* tail item is in use, get a new one */ + /* the tail item is complete; verify it before starting a new one */ + error =3D xlog_recover_verify_item(log, item); + if (error) { + kvfree(ptr); + return error; + } xlog_recover_add_item(&trans->r_itemq); item =3D list_entry(trans->r_itemq.prev, struct xlog_recover_item, ri_list); --=20 2.43.0 From nobody Sat Jul 25 03:46:04 2026 Received: from mail-pl1-f181.google.com (mail-pl1-f181.google.com [209.85.214.181]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C53093914ED for ; Sun, 19 Jul 2026 11:30:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784460634; cv=none; b=CcnKi/5vxjJIb94KG8gfl71VnQSD/UNWgGeA2Rj2LYVEUa0GFGXFcIgP8mx259aB7FEIZ52GFluwrmNn5LiBx1MwF5BJjw6SI8US6D4arQ6Dy8yqDiYltLTx+PI9Ak6yYLE8+e+QQkXNuibxC5JT6ytRubukdkG6AS5Cjtv17hE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784460634; c=relaxed/simple; bh=4hQ/kd46tBOE54pT9yNBScCOaCV7TAEvDaGupEqi+l8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ZprQSaXJiSqIE36EmAPxr5+//WQN2aiQPKqh+TW/zZnWQO0b8BnObRGNsto9m+F3fRXLcnhtwelcAyngFB8Wl/XDw6fMc/qIj9NlXTyRvvvx2mlYt4A5NfbLl3NPaLE5qCTbXA2FXqkft+YONs7E4LU+FHO0MKzCDrRfG8bCaFM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=sujxJqox; arc=none smtp.client-ip=209.85.214.181 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="sujxJqox" Received: by mail-pl1-f181.google.com with SMTP id d9443c01a7336-2cca0c5799eso67907685ad.0 for ; Sun, 19 Jul 2026 04:30:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784460627; x=1785065427; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=LfwjIb9K32R1YmqSTFqp7DJ6+dJC07T6UPOCV+XSImc=; b=sujxJqoxkVE1efe3AvZZhaSciTdz98+357t+7rC4sFKecZ7F5tyuXzGdivO8VSxLBS v+lOxmS20YFul7Wjh55D8tMXguAjy1bfiqVUIOH+2AkpDbHoWA1e0Btc1Sh6i9e5hZdX lisU13Npr0Lta+KOTCeEMoVhwVgAtHwidvtzdv9w9snbAOe+bcRsYpF9nraqDnN5estG bxV8jlidI8Lra+DQOzjGQDftI7pz9qc/63w7K++T8EVzrI40w3PThFkIYZTdmLvxwK14 I8oZmQVHrPcE0EHe9RgPhF9STOKQc0zfaH4nb7oXgX7DAMeGrrLqUnv6QvcgI2WhrTYA 2Rgw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784460627; x=1785065427; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=LfwjIb9K32R1YmqSTFqp7DJ6+dJC07T6UPOCV+XSImc=; b=Z8NchH0bEDokCcpIynIigrfUtsazK4Hv/yubyzOUkP39RBrT8inzHnZlESrXVZmqR0 svFm+lWe0mxmu3mTtfwwfeIT4tnOAkwKqznm4i2zDAShi5t72P0jipxMnXrLcXJEQ5Qk kTQcu7u2sHob6Xayo5ayNqE+mKAR34FuehW5dyQDeQmZy5DVsVa4n7VaUT80XoZxNd1H W1jiPaw4/2dWgK/1dbCdoG+pKWyFQhq0QhNQtbZ/K6p+iQIKoAQLn8ueiTl12VmteWr/ Bv0Hdns/tQXk1ldO29dSoyg6DBzEfYCHT3euqcOa8CtcmMns8ZFHLVme5//LBwnO/lDf NaHw== X-Forwarded-Encrypted: i=1; AHgh+Rr1/9AWyPozkJQA1h8PlW2/TaAs8tZKtCZmj/DfvP38cpUO5zYqHhYSFM0YkIYBapk3WKQNRdvstvghokA=@vger.kernel.org X-Gm-Message-State: AOJu0YyI3Vivb8tjMT5wYUNqz8iZWjri+x6PX1+5ldK92XkiKgW7S5LG a7m1ta7xYWhV0mqgbBZ+5nG7wtVuO8ZGnILtNw3S6e0L+aRiAeMQTohO X-Gm-Gg: AfdE7ckJgTl8xo04+pxgrCU3uEOQ7NuhIkqtDj1n39Y7/gd6uQdk2LuNFOaJeXDp/Hy WGcyUVUUgathqaYbuuljv//LyHOsTE/E2ZaDP18LgCB+qo3xWnkOqAMkOBb64HXb/QEimITjyxM Pny+r9sEWpCUMU2rYCtxOnqKuGNzgND+dmQrMFupuq0cisFiZPfeK2K5h9R8zkP5+QkkEvt7t8f 7gLtrue739gOqxPUbwSuJMflLYO1ur2Vv6/s/QvyL3IjGaCn7g7ZhZQ6bAloXX0G7De9Xgk+m9Y sFQ+rN4+thsLVI5u7olHyq5tgzEGlqvVxXf82k/4HdaBz6D6KvAZC10aDtpiFwN5+HS5pKAOevE 6n3t5yk5wgQwurWmBtSCdljzoyp8CJ3NQNznXYJQPqFoJ2dFhMKDKDc407Y5JAzP6nZCTIFXgLV ChJFUeI1JHCgonmWAH18vt2g3ES74h+lZQ3RuSklchMCD3ED2LiG3+mgI1Jg== X-Received: by 2002:a17:902:e80e:b0:2cc:8267:31b5 with SMTP id d9443c01a7336-2cf3489a830mr103197365ad.19.1784460627295; Sun, 19 Jul 2026 04:30:27 -0700 (PDT) Received: from fx.tailc0aff1.ts.net ([206.206.192.132]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13ce2ddfb35sm22035031c88.14.2026.07.19.04.30.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 19 Jul 2026 04:30:26 -0700 (PDT) From: Weiming Shi To: Carlos Maiolino , "Darrick J . Wong" Cc: linux-xfs@vger.kernel.org, linux-kernel@vger.kernel.org, xmei5@asu.edu, Weiming Shi , Dave Chinner Subject: [PATCH v3 3/3] xfs: add an inode log item recovery verifier Date: Sun, 19 Jul 2026 04:29:23 -0700 Message-ID: <20260719112923.226550-4-bestswngs@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260719112923.226550-1-bestswngs@gmail.com> References: <20260719112923.226550-1-bestswngs@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The previous patches let each log item type validate its own formatted structures through an xlog_recover_item_ops->verify method, run once the item is fully decoded. Add the first verifier, for inode items. Log recovery previously validated a recovered inode item's structures inside the pass2 decode and replay code, one open-coded check at a time, which was hard to read and to audit for what was still unchecked. xlog_recover_inode_verify() instead checks in one place that the core and each fork region implied by ilf_fields is declared, that the log dinode is present and large enough, that its version matches the mount, that di_forkoff is within the literal area, and that the verbatim-copied fork regions fit their destination fork. Because the log dinode checks now run before pass2, drop the equivalent open-coded checks (the log dinode magic and the dead di_forkoff bound) from xlog_recover_inode_commit_pass2(). The checks that need the on-disk inode buffer (its magic, the LSN and di_flushiter replay-ordering decisions, the di_mode/di_format consistency, and the final xfs_dinode_verify()) cannot be hoisted and stay in pass2. This covers the self-contained log dinode structure. The btree-root fork formats are converted from a larger in-core form on replay and their record count is not bounded here yet; clamping xfs_bmbt_to_bmdr() and the rt btree converters against the destination fork is left as follow-up. Suggested-by: Dave Chinner Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Weiming Shi --- fs/xfs/xfs_inode_item_recover.c | 87 +++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/fs/xfs/xfs_inode_item_recover.c b/fs/xfs/xfs_inode_item_recove= r.c index 6b6ac92964d0..8308f064cecc 100644 --- a/fs/xfs/xfs_inode_item_recover.c +++ b/fs/xfs/xfs_inode_item_recover.c @@ -367,13 +367,6 @@ xlog_recover_inode_commit_pass2( goto out_release; } ldip =3D item->ri_buf[1].iov_base; - if (XFS_IS_CORRUPT(mp, ldip->di_magic !=3D XFS_DINODE_MAGIC)) { - xfs_alert(mp, - "%s: Bad inode log record, rec ptr "PTR_FMT", ino %lld", - __func__, item, in_f->ilf_ino); - error =3D -EFSCORRUPTED; - goto out_release; - } =20 /* * If the inode has an LSN in it, recover the inode only if the on-disk @@ -462,15 +455,6 @@ xlog_recover_inode_commit_pass2( if (error) goto out_release; =20 - if (unlikely(ldip->di_forkoff > mp->m_sb.sb_inodesize)) { - XFS_CORRUPTION_ERROR("Bad log dinode fork offset", - XFS_ERRLEVEL_LOW, mp, ldip, sizeof(*ldip)); - xfs_alert(mp, - "Bad inode 0x%llx, di_forkoff 0x%x", - in_f->ilf_ino, ldip->di_forkoff); - error =3D -EFSCORRUPTED; - goto out_release; - } isize =3D xfs_log_dinode_size(mp); if (unlikely(item->ri_buf[1].iov_len > isize)) { XFS_CORRUPTION_ERROR("Bad log dinode size", XFS_ERRLEVEL_LOW, @@ -597,10 +581,81 @@ xlog_recover_inode_commit_pass2( return error; } =20 +/* + * Validate the log dinode and fork regions of a decoded inode item. Chec= ks + * that need the on-disk inode buffer stay in xlog_recover_inode_commit_pa= ss2(). + */ +STATIC int +xlog_recover_inode_verify( + struct xlog *log, + struct xlog_recover_item *item) +{ + struct xfs_mount *mp =3D log->l_mp; + struct xfs_inode_log_format *in_f; + struct xfs_inode_log_format in_f_buf; + struct xfs_log_dinode *ldip; + unsigned int litino =3D XFS_LITINO(mp); + unsigned int dsize, asize; + int attr_index; + int error; + + if (item->ri_buf[0].iov_len =3D=3D sizeof(struct xfs_inode_log_format)) { + in_f =3D item->ri_buf[0].iov_base; + } else { + in_f =3D &in_f_buf; + error =3D xfs_inode_item_format_convert(&item->ri_buf[0], in_f); + if (error) + return error; + } + + /* The inode core is always logged as the log dinode in ri_buf[1]. */ + if (XFS_IS_CORRUPT(mp, in_f->ilf_size < 2) || + XFS_IS_CORRUPT(mp, + item->ri_buf[1].iov_len < xfs_log_dinode_size(mp))) + return -EFSCORRUPTED; + + ldip =3D item->ri_buf[1].iov_base; + if (XFS_IS_CORRUPT(mp, ldip->di_magic !=3D XFS_DINODE_MAGIC) || + XFS_IS_CORRUPT(mp, !xfs_dinode_good_version(mp, ldip->di_version)) || + XFS_IS_CORRUPT(mp, ldip->di_forkoff >=3D (litino >> 3))) + return -EFSCORRUPTED; + + if (ldip->di_forkoff) { + dsize =3D ldip->di_forkoff << 3; + asize =3D litino - (ldip->di_forkoff << 3); + } else { + dsize =3D litino; + asize =3D 0; + } + + /* + * Btree-root forks are logged in a larger in-core form and converted on + * replay, so their region is not bounded by the on-disk fork size here. + */ + if (in_f->ilf_fields & XFS_ILOG_DFORK) { + if (XFS_IS_CORRUPT(mp, in_f->ilf_size < 3)) + return -EFSCORRUPTED; + if ((in_f->ilf_fields & XFS_ILOG_DFORK) !=3D XFS_ILOG_DBROOT && + XFS_IS_CORRUPT(mp, item->ri_buf[2].iov_len > dsize)) + return -EFSCORRUPTED; + } + if (in_f->ilf_fields & XFS_ILOG_AFORK) { + attr_index =3D (in_f->ilf_fields & XFS_ILOG_DFORK) ? 3 : 2; + if (XFS_IS_CORRUPT(mp, in_f->ilf_size < attr_index + 1)) + return -EFSCORRUPTED; + if ((in_f->ilf_fields & XFS_ILOG_AFORK) !=3D XFS_ILOG_ABROOT && + XFS_IS_CORRUPT(mp, item->ri_buf[attr_index].iov_len > asize)) + return -EFSCORRUPTED; + } + + return 0; +} + const struct xlog_recover_item_ops xlog_inode_item_ops =3D { .item_type =3D XFS_LI_INODE, .max_regions =3D 4, .min_regions =3D 2, + .verify =3D xlog_recover_inode_verify, .ra_pass2 =3D xlog_recover_inode_ra_pass2, .commit_pass2 =3D xlog_recover_inode_commit_pass2, }; --=20 2.43.0