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

"wangjianjian (C)" <[email protected]> Fri, 31 Jul 2026 10:52:28 +0800
Newsgroups org.kernel.vger.linux-nilfs
Message-ID <[email protected]>
在 2026/7/30 20:42, Ryusuke Konishi 写道:
> On Thu, Jul 30, 2026 at 9:32 PM Wang Jianjian wrote:
>>
>> 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
> 
> 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 loaded.
> 
> 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 <= key2)" is
> an exceptional error case, please wrap it with the unlikely() macro.
> 
> Could you please revise your implementation based on these comments?

Thanks for your detailed review, for the 2 patches, I will send v2 based 
your review.

  > Thank you.
> Ryusuke Konishi

-- 
Regards