Re: [PATCH] nilfs2: fix slab-out-of-bounds in nilfs_direct_propagate after truncation
From: Viacheslav Dubeyko
Date: Tue Jul 28 2026 - 19:37:19 EST
On Fri, 2026-07-17 at 13:39 +0900, Ryusuke Konishi wrote:
> Shuangpeng Bai reported that KASAN detected a slab-out-of-bounds
> error
> in nilfs_direct_propagate() during testing.
>
> Analysis revealed that after truncating a file, a node block
> immediately
> below the B-tree root was not deleted. Instead, it remained in the
> B-tree
> node cache in a dirty state. The log writer subsequently detected
> this
> block and incorrectly invoked nilfs_direct_propagate() on it, which
> is
> designed to handle only data blocks in direct mapping.
>
> B-tree nodes in the cache are managed by virtual block numbers, and
> their
> logical keys typically exceed the range expected by direct mapping.
> Consequently, processing such a node as a direct mapping entry
> triggers
> a slab-out-of-bounds access.
>
> The root cause is that when a B-tree mapping collapses into a direct
> mapping during truncation, an intermediate node block pointed to by
> the
> root node is left behind as garbage instead of being explicitly
> deleted.
>
> This resolves the issue by adding a nilfs_btree_discard() operation
> to delete the remaining intermediate node block during the
> conversion.
> A 'deform' flag is added to the bop_delete interface to explicitly
> signal
> that the deletion is part of a mapping transformation. This allows
> the
> B-tree mapping implementation to perform the necessary cleanup and
> discarding of the residual node structure that would be otherwise be
> left
> orphaned after the transition.
>
> Reported-by: Shuangpeng Bai <shuangpeng.kernel@xxxxxxxxx>
> Closes:
> https://lore.kernel.org/r/08A3603A-ADB6-484C-9015-9AC1340E6FB8@xxxxxxxxx
> Fixes: 36a580eb489f ("nilfs2: direct block mapping")
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: Ryusuke Konishi <konishi.ryusuke@xxxxxxxxx>
> ---
> Hi Viacheslav,
>
> Please apply this for the next cycle.
>
> This fixes a flaw in the original B-tree implementation related to
> truncation
> and resolves the reported out-of-bounds memory access issue.
>
> Thanks,
> Ryusuke Konishi
>
> fs/nilfs2/bmap.c | 2 +-
> fs/nilfs2/bmap.h | 2 +-
> fs/nilfs2/btree.c | 39 ++++++++++++++++++++++++++++++++-------
> fs/nilfs2/direct.c | 4 ++--
> 4 files changed, 36 insertions(+), 11 deletions(-)
>
> diff --git a/fs/nilfs2/bmap.c b/fs/nilfs2/bmap.c
> index 5f0f1f283af0..83f6ea30cc8b 100644
> --- a/fs/nilfs2/bmap.c
> +++ b/fs/nilfs2/bmap.c
> @@ -175,7 +175,7 @@ static int nilfs_bmap_do_delete(struct nilfs_bmap
> *bmap, __u64 key)
> return ret;
> }
>
> - return bmap->b_ops->bop_delete(bmap, key);
> + return bmap->b_ops->bop_delete(bmap, key, false);
> }
>
> /**
> diff --git a/fs/nilfs2/bmap.h b/fs/nilfs2/bmap.h
> index 4656df392722..a72f3c308a5d 100644
> --- a/fs/nilfs2/bmap.h
> +++ b/fs/nilfs2/bmap.h
> @@ -63,7 +63,7 @@ struct nilfs_bmap_operations {
> int (*bop_lookup_contig)(const struct nilfs_bmap *, __u64,
> __u64 *,
> unsigned int);
> int (*bop_insert)(struct nilfs_bmap *, __u64, __u64);
> - int (*bop_delete)(struct nilfs_bmap *, __u64);
> + int (*bop_delete)(struct nilfs_bmap *bmap, __u64 key, bool
> deform);
> void (*bop_clear)(struct nilfs_bmap *);
>
> int (*bop_propagate)(struct nilfs_bmap *, struct buffer_head
> *);
> diff --git a/fs/nilfs2/btree.c b/fs/nilfs2/btree.c
> index 64d5f7c5ab44..64bac66af25b 100644
> --- a/fs/nilfs2/btree.c
> +++ b/fs/nilfs2/btree.c
> @@ -1425,6 +1425,28 @@ static void nilfs_btree_shrink(struct
> nilfs_bmap *btree,
> path[level].bp_bh = NULL;
> }
>
> +/**
> + * nilfs_btree_discard - discard the last node for the mapping
> transformation
> + * @btree: bmap struct of btree
> + * @path: array of nilfs_btree_path struct
> + * @level: level of the B-tree node being operated on
> + * @keyp: argument for passing a key (unused)
> + * @ptrp: argument for passing a pointer (unused)
> + */
> +static void nilfs_btree_discard(struct nilfs_bmap *btree,
> + struct nilfs_btree_path *path, int
> level,
> + __u64 *keyp, __u64 *ptrp)
> +{
> + struct nilfs_btree_node *root = nilfs_btree_get_root(btree);
> +
> + nilfs_btree_node_delete(root, 0, NULL, NULL,
> + NILFS_BTREE_ROOT_NCHILDREN_MAX);
> + nilfs_btree_node_set_level(root, level);
> +
> + nilfs_btnode_delete(path[level].bp_bh);
> + path[level].bp_bh = NULL;
> +}
> +
> static void nilfs_btree_nop(struct nilfs_bmap *btree,
> struct nilfs_btree_path *path,
> int level, __u64 *keyp, __u64 *ptrp)
> @@ -1435,7 +1457,7 @@ static int nilfs_btree_prepare_delete(struct
> nilfs_bmap *btree,
> struct nilfs_btree_path *path,
> int *levelp,
> struct nilfs_bmap_stats
> *stats,
> - struct inode *dat)
> + struct inode *dat, bool
> deform)
> {
> struct buffer_head *bh;
> struct nilfs_btree_node *node, *parent, *sib;
> @@ -1522,15 +1544,17 @@ static int nilfs_btree_prepare_delete(struct
> nilfs_bmap *btree,
> if (nilfs_btree_node_get_nchildren(node) - 1
> <=
> NILFS_BTREE_ROOT_NCHILDREN_MAX) {
> path[level].bp_op =
> nilfs_btree_shrink;
> - stats->bs_nblocks += 2;
> - level++;
> - path[level].bp_op = nilfs_btree_nop;
> - goto shrink_root_child;
> + } else if (deform) {
> + path[level].bp_op =
> nilfs_btree_discard;
> } else {
> path[level].bp_op =
> nilfs_btree_do_delete;
> stats->bs_nblocks++;
> goto out;
> }
> + stats->bs_nblocks += 2;
> + level++;
> + path[level].bp_op = nilfs_btree_nop;
> + goto shrink_root_child;
> }
> }
>
> @@ -1581,7 +1605,7 @@ static void nilfs_btree_commit_delete(struct
> nilfs_bmap *btree,
> nilfs_bmap_set_dirty(btree);
> }
>
> -static int nilfs_btree_delete(struct nilfs_bmap *btree, __u64 key)
> +static int nilfs_btree_delete(struct nilfs_bmap *btree, __u64 key,
> bool deform)
>
> {
> struct nilfs_btree_path *path;
> @@ -1601,7 +1625,8 @@ static int nilfs_btree_delete(struct nilfs_bmap
> *btree, __u64 key)
>
> dat = NILFS_BMAP_USE_VBN(btree) ? nilfs_bmap_get_dat(btree)
> : NULL;
>
> - ret = nilfs_btree_prepare_delete(btree, path, &level,
> &stats, dat);
> + ret = nilfs_btree_prepare_delete(btree, path, &level,
> &stats, dat,
> + deform);
> if (ret < 0)
> goto out;
> nilfs_btree_commit_delete(btree, path, level, dat);
> diff --git a/fs/nilfs2/direct.c b/fs/nilfs2/direct.c
> index 8bd0b1374e25..b8643d3aa2f8 100644
> --- a/fs/nilfs2/direct.c
> +++ b/fs/nilfs2/direct.c
> @@ -144,7 +144,7 @@ static int nilfs_direct_insert(struct nilfs_bmap
> *bmap, __u64 key, __u64 ptr)
> return ret;
> }
>
> -static int nilfs_direct_delete(struct nilfs_bmap *bmap, __u64 key)
> +static int nilfs_direct_delete(struct nilfs_bmap *bmap, __u64 key,
> bool deform)
> {
> union nilfs_bmap_ptr_req req;
> struct inode *dat;
> @@ -234,7 +234,7 @@ int nilfs_direct_delete_and_convert(struct
> nilfs_bmap *bmap,
> /* no need to allocate any resource for conversion */
>
> /* delete */
> - ret = bmap->b_ops->bop_delete(bmap, key);
> + ret = bmap->b_ops->bop_delete(bmap, key, true);
> if (ret < 0)
> return ret;
>
Applied.
Thanks,
Slava.