mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
nilfs2: fix slab-out-of-bounds in nilfs_direct_propagate after truncation
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@gmail.com>
Closes: https://lore.kernel.org/r/08A3603A-ADB6-484C-9015-9AC1340E6FB8@gmail.com
Fixes: 36a580eb48 ("nilfs2: direct block mapping")
Cc: stable@vger.kernel.org
Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
This commit is contained in:
committed by
Viacheslav Dubeyko
parent
6e2b150136
commit
45662dedb8
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -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 *);
|
||||
|
||||
+32
-7
@@ -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);
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user