[PATCH 2/2] dm btree: reject a node whose value size is not the reading level's
Bryam Vargas via B4 Relay <[email protected]> Fri, 31 Jul 2026 21:32:04 -0500
| Newsgroups | dev.linux.lists.dm-devel,org.kernel.feeds.b4-sent,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Bryam Vargas <[email protected]> value_ptr() takes an entry's address from the value_size on disk while the caller supplies the length it copies; a validator cannot compare the two, since it sees the block and never the caller. A leaf can pass every check the previous patch adds and still be laid out for a narrower value, leaving insert_at() and btree_split_beneath() striding with the caller's size over a base placed by the node's -- writes that land kilobytes past the block. Compare the sizes wherever both are in hand. Everything that modifies a node reaches it through bn_shadow(), so one check there covers insert, the splits, remove and the space map's refcount overflow leaf; the readers and the two paths that bypass the spine take it where the expected size is known. A node is only ever read through the level that wrote it, so conforming metadata is unaffected. Fixes: 3241b1d3e0aa ("dm: add persistent data library") Cc: [email protected] Signed-off-by: Bryam Vargas <[email protected]> --- drivers/md/persistent-data/dm-btree-internal.h | 6 +- drivers/md/persistent-data/dm-btree-remove.c | 24 ++++++-- drivers/md/persistent-data/dm-btree-spine.c | 13 ++++- drivers/md/persistent-data/dm-btree.c | 79 +++++++++++++++++++++++--- 4 files changed, 108 insertions(+), 14 deletions(-) diff --git a/drivers/md/persistent-data/dm-btree-internal.h b/drivers/md/persistent-data/dm-btree-internal.h index 404739149d02..7d5741572b16 100644 --- a/drivers/md/persistent-data/dm-btree-internal.h +++ b/drivers/md/persistent-data/dm-btree-internal.h @@ -51,8 +51,10 @@ uint32_t calc_max_entries(size_t value_size, size_t block_size); int bn_read_lock(struct dm_btree_info *info, dm_block_t b, struct dm_block **result); -void inc_children(struct dm_transaction_manager *tm, struct btree_node *n, - struct dm_btree_value_type *vt); +int check_value_size(struct btree_node *n, size_t expected); + +int inc_children(struct dm_transaction_manager *tm, struct btree_node *n, + struct dm_btree_value_type *vt); int new_block(struct dm_btree_info *info, struct dm_block **result); void unlock_block(struct dm_btree_info *info, struct dm_block *b); diff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index aeec5b9a1dd5..64f3313f5a1c 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -175,8 +175,14 @@ static int init_child(struct dm_btree_info *info, struct dm_btree_value_type *vt result->n = dm_block_data(result->block); - if (inc) - inc_children(info->tm, result->n, vt); + r = check_value_size(result->n, vt->size); + if (!r && inc) + r = inc_children(info->tm, result->n, vt); + + if (r) { + dm_tm_unlock(info->tm, result->block); + return r; + } *((__le64 *) value_ptr(parent, index)) = cpu_to_le64(dm_block_location(result->block)); @@ -501,8 +507,18 @@ static int rebalance_children(struct shadow_spine *s, if (r) return r; - if (is_shared) - inc_children(info->tm, dm_block_data(child), vt); + /* + * The child is copied over the node the spine already + * checked, so it has to answer for itself first. + */ + r = check_value_size(dm_block_data(child), vt->size); + if (!r && is_shared) + r = inc_children(info->tm, dm_block_data(child), vt); + + if (r) { + dm_tm_unlock(info->tm, child); + return r; + } memcpy(n, dm_block_data(child), dm_bm_block_size(dm_tm_get_bm(info->tm))); diff --git a/drivers/md/persistent-data/dm-btree-spine.c b/drivers/md/persistent-data/dm-btree-spine.c index 076f836912c8..2527af9460fe 100644 --- a/drivers/md/persistent-data/dm-btree-spine.c +++ b/drivers/md/persistent-data/dm-btree-spine.c @@ -121,8 +121,19 @@ static int bn_shadow(struct dm_btree_info *info, dm_block_t orig, r = dm_tm_shadow_block(info->tm, orig, &btree_node_validator, result, &inc); + if (r) + return r; + + /* + * Everything that modifies a node reaches it through here, so this is + * where a leaf laid out for a different value size is caught. + */ + r = check_value_size(dm_block_data(*result), vt->size); if (!r && inc) - inc_children(info->tm, dm_block_data(*result), vt); + r = inc_children(info->tm, dm_block_data(*result), vt); + + if (r) + unlock_block(info, *result); return r; } diff --git a/drivers/md/persistent-data/dm-btree.c b/drivers/md/persistent-data/dm-btree.c index 5ed3b3e9abb9..b74c41fe6213 100644 --- a/drivers/md/persistent-data/dm-btree.c +++ b/drivers/md/persistent-data/dm-btree.c @@ -71,16 +71,47 @@ static int upper_bound(struct btree_node *n, uint64_t key) return bsearch(n, key, 1); } -void inc_children(struct dm_transaction_manager *tm, struct btree_node *n, - struct dm_btree_value_type *vt) +/* + * value_ptr() takes an entry's address from the value_size stored on disk, + * but the caller supplies the length it copies. node_check() sees the block + * and never the caller, so the two are compared here, wherever both are in + * hand. Internal nodes are exempt: node_check() already holds them to + * sizeof(__le64), which is what value64() assumes. + */ +int check_value_size(struct btree_node *n, size_t expected) +{ + uint32_t value_size = le32_to_cpu(n->header.value_size); + + if (le32_to_cpu(n->header.flags) & INTERNAL_NODE) + return 0; + + if (value_size != expected) { + DMERR_LIMIT("%s failed: value_size %u != %zu expected by the caller", + __func__, value_size, expected); + return -EILSEQ; + } + + return 0; +} + +int inc_children(struct dm_transaction_manager *tm, struct btree_node *n, + struct dm_btree_value_type *vt) { uint32_t nr_entries = le32_to_cpu(n->header.nr_entries); if (le32_to_cpu(n->header.flags) & INTERNAL_NODE) dm_tm_with_runs(tm, value_ptr(n, 0), nr_entries, dm_tm_inc_range); - else if (vt->inc) + else if (vt->inc) { + int r = check_value_size(n, vt->size); + + if (r) + return r; + vt->inc(vt->context, value_ptr(n, 0), nr_entries); + } + + return 0; } static int insert_at(size_t value_size, struct btree_node *node, unsigned int index, @@ -314,6 +345,10 @@ int dm_btree_del(struct dm_btree_info *info, dm_block_t root) goto out; } else if (is_internal_level(info, f)) { + r = check_value_size(f->n, sizeof(__le64)); + if (r) + goto out; + b = value64(f->n, f->current_child); f->current_child++; r = push_frame(s, b, f->level + 1); @@ -321,9 +356,14 @@ int dm_btree_del(struct dm_btree_info *info, dm_block_t root) goto out; } else { - if (info->value_type.dec) + if (info->value_type.dec) { + r = check_value_size(f->n, info->value_type.size); + if (r) + goto out; + info->value_type.dec(info->value_type.context, value_ptr(f->n, 0), f->nr_children); + } pop_frame(s); } } @@ -365,8 +405,13 @@ static int btree_lookup_raw(struct ro_spine *s, dm_block_t block, uint64_t key, } while (!(flags & LEAF_NODE)); *result_key = le64_to_cpu(ro_node(s)->keys[i]); - if (v) + if (v) { + r = check_value_size(ro_node(s), value_size); + if (r) + return r; + memcpy(v, value_ptr(ro_node(s), i), value_size); + } return 0; } @@ -460,6 +505,10 @@ static int dm_btree_lookup_next_single(struct dm_btree_info *info, dm_block_t ro } *rkey = le64_to_cpu(n->keys[i]); + r = check_value_size(n, info->value_type.size); + if (r) + goto out; + memcpy(value_le, value_ptr(n, i), info->value_type.size); } out: @@ -721,8 +770,14 @@ static int shadow_child(struct dm_btree_info *info, struct dm_btree_value_type * node = dm_block_data(*result); - if (inc) - inc_children(info->tm, node, vt); + r = check_value_size(node, vt->size); + if (!r && inc) + r = inc_children(info->tm, node, vt); + + if (r) { + unlock_block(info, *result); + return r; + } *((__le64 *) value_ptr(parent, index)) = cpu_to_le64(dm_block_location(*result)); @@ -1441,6 +1496,10 @@ static int walk_node(struct dm_btree_info *info, dm_block_t block, if (r) goto out; } else { + r = check_value_size(n, info->value_type.size); + if (r) + goto out; + keys = le64_to_cpu(*key_ptr(n, i)); r = fn(context, &keys, value_ptr(n, i)); if (r) @@ -1474,6 +1533,9 @@ static void prefetch_values(struct dm_btree_cursor *c) BUG_ON(c->info->value_type.size != sizeof(value_le)); + if (check_value_size(bn, sizeof(value_le))) + return; + nr = le32_to_cpu(bn->header.nr_entries); for (i = 0; i < nr; i++) { memcpy(&value_le, value_ptr(bn, i), sizeof(value_le)); @@ -1627,6 +1689,9 @@ int dm_btree_cursor_get_value(struct dm_btree_cursor *c, uint64_t *key, void *va if (le32_to_cpu(bn->header.flags) & INTERNAL_NODE) return -EINVAL; + if (check_value_size(bn, c->info->value_type.size)) + return -EILSEQ; + *key = le64_to_cpu(*key_ptr(bn, n->index)); memcpy(value_le, value_ptr(bn, n->index), c->info->value_type.size); return 0; -- 2.55.0