From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from m16.mail.163.com (m16.mail.163.com [220.197.31.5]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CF66744470C for ; Sat, 26 Sep 2026 13:20:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=220.197.31.5 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790428809; cv=none; b=LrlkEADSQoR1Sh5WPbX7QuJBnkEmIUHLnkWyqFEvMNyuNfMMFPHTIXdy5s3ihCTKaOfCZKqagcoRD670qaKNHVY7iQyppAedG0s16yipmqn6DU5hWbm9atubgnC9asHdmtwjpWpXKEI3ePVZnpkOUdz4tizbMmmGwBG6Ahz8ZfA= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790428809; c=relaxed/simple; bh=Ag+HbidyTvBaqT5NW9TShkPeX7VzVuUFE9FYOTXUJAw=; h=From:To:Cc:Subject:Date:Message-Id:MIME-Version; b=LzO42oX/rWGCiwsKKq21sjp/gmbhnOzl11h9B0lqKW9deIkLbxA9j2XmWYZNO3Amw0qhRE/NVpLgnrJOnMmPWFsT9cYu5LavUS3JWec40CG3y4uHrtNoPaoM+yeZKw8IfMosdLLPS/o0vB5uNY4U6nKgfkuwthwgr9rqBX0g+ck= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=LkrVJlXb; arc=none smtp.client-ip=220.197.31.5 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="LkrVJlXb" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:To:Subject:Date:Message-Id:MIME-Version; bh=iP Z0JZ7WM/fQXNY4w67+kzgznitoNOikcSV8jn8csew=; b=LkrVJlXbI+qvuQod74 JDF9VoGfH0WTL1TSGtNLYbLMCge43OYIdHe6atHGJBl1UK6VjXjukul5Ur76tCxl Kd/1JZARvDQCu8JZuyfrLjBx0g/mFitYZPBbRbX/K0uYRehQMjjJo92GlmD5qnVa ZuzIFfIpo8lFDY+ySSV3Vcl/4= Received: from pc.localdomain (unknown []) by gzga-smtp-mtada-g0-0 (Coremail) with SMTP id _____wD3P0g5xrdq4wWDAw--.54767S2; Sat, 26 Sep 2026 21:18:51 +0800 (CST) From: Jiale Yao To: Mark Fasheh , Joel Becker , Joseph Qi , Gang He , Andrew Morton , ocfs2-devel@lists.linux.dev, linux-kernel@vger.kernel.org Cc: Jiale Yao Subject: [PATCH] ocfs2: validate extent list in filecheck repair Date: Sat, 26 Sep 2026 21:18:47 +0800 Message-Id: <20260926131848.3379842-1-yaojiale02@163.com> X-Mailer: git-send-email 2.34.1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-CM-TRANSID:_____wD3P0g5xrdq4wWDAw--.54767S2 X-Coremail-Antispam: 1Uf129KBjvJXoWxJFyDAFyDKFWfCFykXF18Zrb_yoWrAFW7pF 1akr95Xr18G34xuas3t34DZr1fKr1xCrW7GF4Sk3s8GF1DJrZ0kFyqqFy7XF4ftrWrZrnF 9F15uFZrCa4fAa7anT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x0pEL0ehUUUUU= X-CM-SenderInfo: x1dryxhdohiji6rwjhhfrp/xtbCzhtIgmq3xjuG+AAA3B ocfs2_filecheck_validate_inode_block() does not validate the embedded extent list, while ocfs2_filecheck_repair_inode_block() only clamps l_next_free_rec to l_count. The normal inode read path requires l_count to be non-zero, limits it to the number of extent records that fit in the inode, and requires l_next_free_rec not to exceed l_count. Without the same checks, filecheck can report SUCCESS while leaving an invalid extent list on disk. A later read through the normal inode validation path rejects the inode and makes the filesystem read-only. Add a shared helper for the filecheck paths to check these invariants. The filecheck validator now rejects all three cases. The repair path still clamps l_next_free_rec, but refuses to repair zero or oversized l_count values. Fixes: d56a8f32e4c6 ("ocfs2: check/fix inode block for online file check") Signed-off-by: Jiale Yao --- fs/ocfs2/inode.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c index 180107a11046..57168ed02915 100644 --- a/fs/ocfs2/inode.c +++ b/fs/ocfs2/inode.c @@ -249,6 +249,41 @@ static int ocfs2_dinode_has_extents(struct ocfs2_dinode *di) return 1; } +enum ocfs2_extent_list_status { + OCFS2_EXTENT_LIST_OK, + OCFS2_EXTENT_LIST_ZERO_COUNT, + OCFS2_EXTENT_LIST_OVERSIZED, + OCFS2_EXTENT_LIST_BAD_NEXT_FREE, +}; + +static enum ocfs2_extent_list_status +ocfs2_check_extent_list(struct super_block *sb, struct ocfs2_dinode *di) +{ + struct ocfs2_extent_list *el = &di->id2.i_list; + u16 count; + u16 next_free; + + if (!ocfs2_dinode_has_extents(di)) + return OCFS2_EXTENT_LIST_OK; + + count = le16_to_cpu(el->l_count); + next_free = le16_to_cpu(el->l_next_free_rec); + if (count == 0) + return OCFS2_EXTENT_LIST_ZERO_COUNT; + /* + * The exact capacity depends on i_xattr_inline_size, another + * unvalidated on-disk field. Inline xattrs only shrink the + * list, so the no-xattr maximum is a safe upper bound that a + * valid l_count never exceeds. + */ + if (count > ocfs2_extent_recs_per_inode(sb)) + return OCFS2_EXTENT_LIST_OVERSIZED; + if (next_free > count) + return OCFS2_EXTENT_LIST_BAD_NEXT_FREE; + + return OCFS2_EXTENT_LIST_OK; +} + /* * here's how inodes get read from disk: * iget5_locked -> find_actor -> OCFS2_FIND_ACTOR @@ -1835,6 +1870,33 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb, goto bail; } + switch (ocfs2_check_extent_list(sb, di)) { + case OCFS2_EXTENT_LIST_OK: + break; + case OCFS2_EXTENT_LIST_ZERO_COUNT: + mlog(ML_ERROR, + "Filecheck: invalid dinode #%llu: extent list l_count is zero\n", + (unsigned long long)bh->b_blocknr); + rc = -OCFS2_FILECHECK_ERR_INVALIDINO; + goto bail; + case OCFS2_EXTENT_LIST_OVERSIZED: + mlog(ML_ERROR, + "Filecheck: invalid dinode #%llu: extent list l_count %u exceeds max %u\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(di->id2.i_list.l_count), + ocfs2_extent_recs_per_inode(sb)); + rc = -OCFS2_FILECHECK_ERR_INVALIDINO; + goto bail; + case OCFS2_EXTENT_LIST_BAD_NEXT_FREE: + mlog(ML_ERROR, + "Filecheck: invalid dinode #%llu: extent list l_next_free_rec %u exceeds l_count %u\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(di->id2.i_list.l_next_free_rec), + le16_to_cpu(di->id2.i_list.l_count)); + rc = -OCFS2_FILECHECK_ERR_INVALIDINO; + goto bail; + } + if (ocfs2_dinode_has_size_without_clusters(sb, di)) { if (S_ISDIR(le16_to_cpu(di->i_mode))) mlog(ML_ERROR, @@ -1893,6 +1955,15 @@ static int ocfs2_filecheck_repair_inode_block(struct super_block *sb, return -OCFS2_FILECHECK_ERR_VALIDFLAG; } + switch (ocfs2_check_extent_list(sb, di)) { + case OCFS2_EXTENT_LIST_OK: + case OCFS2_EXTENT_LIST_BAD_NEXT_FREE: + break; + case OCFS2_EXTENT_LIST_ZERO_COUNT: + case OCFS2_EXTENT_LIST_OVERSIZED: + return -OCFS2_FILECHECK_ERR_INVALIDINO; + } + if (le64_to_cpu(di->i_blkno) != bh->b_blocknr) { di->i_blkno = cpu_to_le64(bh->b_blocknr); changed = 1; -- 2.34.1