[PATCH 1/2] dm btree: hold a node header to the geometry its writers guarantee

Bryam Vargas via B4 Relay <[email protected]> Fri, 31 Jul 2026 21:32:03 -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]>

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: [email protected]
Signed-off-by: Bryam Vargas <[email protected]>
---
 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