Re: [PATCH 1/2] nilfs: enhance btree node keys check
Ryusuke Konishi <[email protected]> Thu, 30 Jul 2026 21:42:12 +0900
| Newsgroups | org.kernel.vger.linux-nilfs |
|---|---|
| Message-ID | <CAKFNMomBf5+K1NC=UXCy2Jktcb6Nq-EVCfxv73Okrg7ZjHdnCw@mail.gmail.com> |
On Thu, Jul 30, 2026 at 9:32=E2=80=AFPM Wang Jianjian wrote: > > syzbot reported a warning on nilfs_btree_assign: > WARNING: fs/nilfs2/btree.c:2302 at nilfs_btree_assign+0x983/0xbe0 fs/nilf= s2/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, 13963797672755= 9/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 a= nd assigns > a new blocknr for the index block, it searches the btree with key=3D0 and= min level=3D2, > and apparently returns ENOENT. > > Therefore, we should perform more checks on the btree nodes and return ea= rly. > > 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 =3D btree->b_inode; > + __u64 key1, key2; > + int i, ncmax; > + > if (unlikely(nilfs_btree_node_get_level(node) !=3D level)) { > dump_stack(); > - nilfs_crit(btree->b_inode->i_sb, > + nilfs_crit(inode->i_sb, > "btree level mismatch (ino=3D%llu): %d !=3D %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 =3D 1; i < nilfs_btree_node_get_nchildren(node); i++) { > + key1 =3D nilfs_btree_node_get_key(node, i); > + key2 =3D nilfs_btree_node_get_key(node, i - 1); > + > + if (key1 <=3D key2) { > + nilfs_crit(inode->i_sb, > + "btree node(ino=3D%llu) level=3D%d key= not sorted: %d/%llu <=3D %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 =3D nilfs_btree_get_node(= btree, path, level + 1, &ncmax); > + > + key1 =3D nilfs_btree_node_get_key(parent, path[level + 1]= .bp_index); > + key2 =3D nilfs_btree_node_get_key(node, 0); > + if (key1 !=3D key2) { > + nilfs_crit(inode->i_sb, > + "btree node(ino=3D%llu) level=3D%d key= =3D%llu not equal to parent's key=3D%llu", > + inode->i_ino, level, key2, key1); > + return 1; > + } > + } > + > return 0; > } > > @@ -582,7 +612,7 @@ static int nilfs_btree_do_lookup(const struct nilfs_b= map *btree, > return ret; > > node =3D 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 =3D nilfs_btree_node_lookup(node, key, &ind= ex); > @@ -630,7 +660,7 @@ static int nilfs_btree_do_lookup_last(const struct ni= lfs_bmap *btree, > if (ret < 0) > return ret; > node =3D 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 =3D nilfs_btree_node_get_nchildren(node) - 1; > ptr =3D nilfs_btree_node_get_ptr(node, index, ncmax); > -- > 2.34.1 Hi, Wang Jianjian. Thank you for proposing the patch. Your patch checks for key consistency within B-tree nodes; however, since this check is performed inside nilfs_btree_bad_node() - which is called during every B-tree search - it imposes unnecessary overhead. Could you please move the check to nilfs_btree_broken_node_block() instead? That function performs the check only once when the B-tree node block is lo= aded. By leveraging the BH_nilfs_checked flag on the buffer head, once the check is performed, it prevents redundant checks as long as the buffer remains in the cache. If a node becomes corrupted after being loaded into memory, that indicates a bug in the B-tree implementation itself; therefore, checking it on every lookup is unnecessary. Note that nilfs_btree_get_block() does not currently take a path argument, but how about passing the parent node's key to it for checking purposes? Also, regarding the check for whether keys in the B-tree node are sorted: instead of calling nilfs_btree_node_get_key() for both keys in each iteration, could you reuse the value of the previous key? While the compiler might optimize this, failing to reuse a variable that has already been accessed feels a bit counterintuitive. One more minor point: since the conditions like "if (key1 <=3D key2)" is an exceptional error case, please wrap it with the unlikely() macro. Could you please revise your implementation based on these comments? Thank you. Ryusuke Konishi