[PATCH 1/2] dm btree: hold a node header to the geometry its writers guarantee
From: Bryam Vargas via B4 Relay
Date: Fri Jul 31 2026 - 22:32:10 EST
From: Bryam Vargas <hexlabsecurity@xxxxxxxxx>
node_check() weighs a node header against itself and nothing else, which
leaves three shapes the writers never produce and the reader accepts: a
zero value_size, which collapses value_ptr()'s stride so max_entries
alone places the value area, off the end of the block; an internal node
whose value_size is not the __le64 stride value64() indexes it with; and
a max_entries that disagrees with calc_max_entries(), the formula every
writer computes it from.
Reject all three. Conforming metadata satisfies them by construction:
dm_btree_empty() and btree_split_beneath() take max_entries from
calc_max_entries() and write sizeof(__le64) for an internal node, and
the split paths copy flags, max_entries and value_size together.
calc_max_entries() gains a declaration in dm-btree-internal.h so the
validator shares the formula rather than open-coding it.
Fixes: 3241b1d3e0aa ("dm: add persistent data library")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Bryam Vargas <hexlabsecurity@xxxxxxxxx>
---
drivers/md/persistent-data/dm-btree-internal.h | 2 ++
drivers/md/persistent-data/dm-btree-spine.c | 20 +++++++++++++++++++-
drivers/md/persistent-data/dm-btree.c | 2 +-
3 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/drivers/md/persistent-data/dm-btree-internal.h b/drivers/md/persistent-data/dm-btree-internal.h
index acebd32858a7..404739149d02 100644
--- a/drivers/md/persistent-data/dm-btree-internal.h
+++ b/drivers/md/persistent-data/dm-btree-internal.h
@@ -43,6 +43,8 @@ struct btree_node {
} __packed __aligned(8);
+uint32_t calc_max_entries(size_t value_size, size_t block_size);
+
/*
* Locks a block using the btree node validator.
*/
diff --git a/drivers/md/persistent-data/dm-btree-spine.c b/drivers/md/persistent-data/dm-btree-spine.c
index c46fc50c274e..076f836912c8 100644
--- a/drivers/md/persistent-data/dm-btree-spine.c
+++ b/drivers/md/persistent-data/dm-btree-spine.c
@@ -57,6 +57,25 @@ static int node_check(const struct dm_block_validator *v,
nr_entries = le32_to_cpu(h->nr_entries);
max_entries = le32_to_cpu(h->max_entries);
value_size = le32_to_cpu(h->value_size);
+ flags = le32_to_cpu(h->flags);
+
+ if (!value_size) {
+ DMERR_LIMIT("%s failed: value_size is zero", __func__);
+ return -EILSEQ;
+ }
+
+ if ((flags & INTERNAL_NODE) && value_size != sizeof(__le64)) {
+ DMERR_LIMIT("%s failed: internal node value_size %zu != %zu",
+ __func__, value_size, sizeof(__le64));
+ return -EILSEQ;
+ }
+
+ if (max_entries != calc_max_entries(value_size, block_size)) {
+ DMERR_LIMIT("%s failed: max_entries %u != wanted %u for value_size %zu",
+ __func__, max_entries,
+ calc_max_entries(value_size, block_size), value_size);
+ return -EILSEQ;
+ }
if (sizeof(struct node_header) +
(sizeof(__le64) + value_size) * max_entries > block_size) {
@@ -72,7 +91,6 @@ static int node_check(const struct dm_block_validator *v,
/*
* The node must be either INTERNAL or LEAF.
*/
- flags = le32_to_cpu(h->flags);
if (!(flags & INTERNAL_NODE) && !(flags & LEAF_NODE)) {
DMERR_LIMIT("%s failed: node is neither INTERNAL or LEAF", __func__);
return -EILSEQ;
diff --git a/drivers/md/persistent-data/dm-btree.c b/drivers/md/persistent-data/dm-btree.c
index dd02eee4a23c..5ed3b3e9abb9 100644
--- a/drivers/md/persistent-data/dm-btree.c
+++ b/drivers/md/persistent-data/dm-btree.c
@@ -114,7 +114,7 @@ static int insert_at(size_t value_size, struct btree_node *node, unsigned int in
* We want 3n entries (for some n). This works more nicely for repeated
* insert remove loops than (2n + 1).
*/
-static uint32_t calc_max_entries(size_t value_size, size_t block_size)
+uint32_t calc_max_entries(size_t value_size, size_t block_size)
{
uint32_t total, n;
size_t elt_size = sizeof(uint64_t) + value_size; /* key + value */
--
2.55.0