[PATCH 1/2] nilfs: enhance btree node keys check

Wang Jianjian <[email protected]> Thu, 30 Jul 2026 20:01:04 +0800
Newsgroups org.kernel.vger.linux-nilfs
Message-ID <[email protected]>
syzbot reported a warning on nilfs_btree_assign:
WARNING: fs/nilfs2/btree.c:2302 at nilfs_btree_assign+0x983/0xbe0 fs/nilfs2/btree.c:2302,

Analysis found that a corrupted file has the following btree layout:
Level2(key/ptr): [ 256/15 ]
Level1(key/ptr): [ 0/8, 1/9, 0/10, 3/11, 4/12, 5/13, 6/14, 139637976727559/16, 0/17 ]

The test truncated the file to 2 bytes, which partially zeroes the first block and
adds the file to the dirty list. When the segment constructor writes it and assigns
a new blocknr for the index block, it searches the btree with key=0 and min level=2,
and apparently returns ENOENT.

Therefore, we should perform more checks on the btree nodes and return early.

Signed-off-by: Wang Jianjian <[email protected]>
---
 fs/nilfs2/btree.c | 40 +++++++++++++++++++++++++++++++++++-----
 1 file changed, 35 insertions(+), 5 deletions(-)

diff --git a/fs/nilfs2/btree.c b/fs/nilfs2/btree.c
index 64d5f7c5ab44..8b164e13663f 100644
--- a/fs/nilfs2/btree.c
+++ b/fs/nilfs2/btree.c
@@ -448,16 +448,46 @@ nilfs_btree_get_node(const struct nilfs_bmap *btree,
 }
 
 static int nilfs_btree_bad_node(const struct nilfs_bmap *btree,
+				const struct nilfs_btree_path *path,
 				struct nilfs_btree_node *node, int level)
 {
+	struct inode *inode = btree->b_inode;
+	__u64 key1, key2;
+	int i, ncmax;
+
 	if (unlikely(nilfs_btree_node_get_level(node) != level)) {
 		dump_stack();
-		nilfs_crit(btree->b_inode->i_sb,
+		nilfs_crit(inode->i_sb,
 			   "btree level mismatch (ino=%llu): %d != %d",
-			   btree->b_inode->i_ino,
-			   nilfs_btree_node_get_level(node), level);
+			   inode->i_ino, nilfs_btree_node_get_level(node), level);
 		return 1;
 	}
+
+	for (i = 1; i < nilfs_btree_node_get_nchildren(node); i++) {
+		key1 = nilfs_btree_node_get_key(node, i);
+		key2 = nilfs_btree_node_get_key(node, i - 1);
+
+		if (key1 <= key2) {
+			nilfs_crit(inode->i_sb,
+				   "btree node(ino=%llu) level=%d key not sorted: %d/%llu <= %d/%llu",
+					inode->i_ino, level, i, key1, i - 1, key2);
+			return 1;
+		}
+	}
+
+	if (level < nilfs_btree_height(btree) - 1) {
+		struct nilfs_btree_node *parent = nilfs_btree_get_node(btree, path, level + 1, &ncmax);
+
+		key1 = nilfs_btree_node_get_key(parent, path[level + 1].bp_index);
+		key2 = nilfs_btree_node_get_key(node, 0);
+		if (key1 != key2) {
+			nilfs_crit(inode->i_sb,
+				   "btree node(ino=%llu) level=%d key=%llu not equal to parent's key=%llu",
+				   inode->i_ino, level, key2, key1);
+			return 1;
+		}
+	}
+
 	return 0;
 }
 
@@ -582,7 +612,7 @@ static int nilfs_btree_do_lookup(const struct nilfs_bmap *btree,
 			return ret;
 
 		node = nilfs_btree_get_nonroot_node(path, level);
-		if (nilfs_btree_bad_node(btree, node, level))
+		if (nilfs_btree_bad_node(btree, path, node, level))
 			return -EINVAL;
 		if (!found)
 			found = nilfs_btree_node_lookup(node, key, &index);
@@ -630,7 +660,7 @@ static int nilfs_btree_do_lookup_last(const struct nilfs_bmap *btree,
 		if (ret < 0)
 			return ret;
 		node = nilfs_btree_get_nonroot_node(path, level);
-		if (nilfs_btree_bad_node(btree, node, level))
+		if (nilfs_btree_bad_node(btree, path, node, level))
 			return -EINVAL;
 		index = nilfs_btree_node_get_nchildren(node) - 1;
 		ptr = nilfs_btree_node_get_ptr(node, index, ncmax);
-- 
2.34.1