[PATCH 4/6] partitions: check new partitions against usable LBAs, not device size

Sascha Hauer s.hauer at pengutronix.de
Wed Jul 1 02:27:37 PDT 2026


parted could create partitions using the raw device size instead of the
partition table's usable LBA range. Two paths were affected:

  - "mkpart ... max" computed the end as blk->num_blocks - 35 (GPT) or
    blk->num_blocks - 1 (MBR), hardcoding GPT geometry instead of using
    the table's last_usable_lba.

  - "mkpart_size" placed partitions via partition_find_free_space() /
    partition_is_free(), which bounded against blk->num_blocks.

For GPT these device-size bounds can exceed last_usable_lba (e.g. for a
table read from a device larger than the one it was created for, whose
last_usable_lba still reflects the smaller size, or a table reserving a
larger partition entry array). A partition placed there overlaps the
backup GPT header and partition entry array at the end of the device.
Linux does not reject such a partition - is_pte_valid() only bounds
entries against the physical device end - so the overlap surfaces as
silent corruption once the backup GPT is written.

Expose first_usable_lba/last_usable_lba on struct partition_desc,
populated by each parser from its own table geometry (GPT from the
header, others defaulting to the whole device), and validate partition
placement against them in partition_create(), partition_is_free(),
partition_find_free_space() and the parted "max" handler.

The lower bound remains the maximum of the table's first_usable_lba and
the global partitions.first_usable_lba, so space reserved at the start
of the device is still honoured even when the table would allow a lower
start.

Signed-off-by: Sascha Hauer <sascha at saschahauer.de>
---
 commands/parted.c       |  8 +-------
 common/partitions.c     | 28 +++++++++++++++++++++-------
 common/partitions/efi.c |  6 ++++++
 include/partitions.h    |  2 ++
 4 files changed, 30 insertions(+), 14 deletions(-)

diff --git a/commands/parted.c b/commands/parted.c
index dd79def62a..423eff7aa0 100644
--- a/commands/parted.c
+++ b/commands/parted.c
@@ -165,13 +165,7 @@ static int do_mkpart(struct block_device *blk, int argc, char *argv[])
 	start >>= SECTOR_SHIFT;
 
 	if (!strcmp(argv[4], "max")) {
-		/* gpt requires 34 blocks at the end */
-		if (pdesc->parser->type == filetype_gpt)
-			end = blk->num_blocks - 35;
-		else if (pdesc->parser->type == filetype_mbr)
-			end = blk->num_blocks - 1;
-		else
-			return -ENOSYS;
+		end = pdesc->last_usable_lba;
 	} else {
 		ret = parted_strtoull(argv[4], &end, &mult);
 		if (ret)
diff --git a/common/partitions.c b/common/partitions.c
index 40f4c629e1..82dadd515b 100644
--- a/common/partitions.c
+++ b/common/partitions.c
@@ -189,7 +189,10 @@ bool partition_is_free(struct partition_desc *pdesc, uint64_t start, uint64_t si
 	if (start < PARTITION_ALIGN_SECTORS)
 		return false;
 
-	if (start + size >= pdesc->blk->num_blocks)
+	if (start < pdesc->first_usable_lba)
+		return false;
+
+	if (start + size - 1 > pdesc->last_usable_lba)
 		return false;
 
 	list_for_each_entry(p, &pdesc->partitions, list) {
@@ -204,9 +207,10 @@ int partition_find_free_space(struct partition_desc *pdesc, uint64_t sectors, ui
 {
 	struct partition *p;
 	uint64_t min_sec = PARTITION_ALIGN_SECTORS;
+	uint64_t first_usable = max(partition_first_usable_lba(), pdesc->first_usable_lba);
 
-	if (min_sec < partition_first_usable_lba())
-		min_sec = partition_first_usable_lba();
+	if (min_sec < first_usable)
+		min_sec = first_usable;
 
 	min_sec = ALIGN(min_sec, PARTITION_ALIGN_SECTORS);
 
@@ -231,6 +235,7 @@ int partition_create(struct partition_desc *pdesc, const char *name,
 		     const char *fs_type, uint64_t lba_start, uint64_t lba_end)
 {
 	struct partition *part;
+	uint64_t first_usable;
 
 	if (!pdesc->parser->mkpart)
 		return -ENOSYS;
@@ -240,14 +245,16 @@ int partition_create(struct partition_desc *pdesc, const char *name,
 		return -EINVAL;
 	}
 
-	if (lba_end >= pdesc->blk->num_blocks) {
-		pr_err("lba_end exceeds device: %llu >= %llu\n", lba_end, pdesc->blk->num_blocks);
+	if (lba_end > pdesc->last_usable_lba) {
+		pr_err("lba_end exceeds last usable lba: %llu > %llu\n",
+		       lba_end, pdesc->last_usable_lba);
 		return -EINVAL;
 	}
 
-	if (lba_start < partition_first_usable_lba()) {
+	first_usable = max(partition_first_usable_lba(), pdesc->first_usable_lba);
+	if (lba_start < first_usable) {
 		pr_err("partition starts before first usable lba: %llu < %llu\n",
-		       lba_start, partition_first_usable_lba());
+		       lba_start, first_usable);
 		return -EINVAL;
 	}
 
@@ -289,6 +296,13 @@ void partition_table_free(struct partition_desc *pdesc)
 void partition_desc_init(struct partition_desc *pd, struct block_device *blk)
 {
 	pd->blk = blk;
+	/*
+	 * Default usable range spanning the whole device. Parsers that know
+	 * better (e.g. GPT reserves space for its headers and partition entry
+	 * arrays) override these from the on-disk table geometry.
+	 */
+	pd->first_usable_lba = partition_first_usable_lba();
+	pd->last_usable_lba = blk->num_blocks - 1;
 	INIT_LIST_HEAD(&pd->partitions);
 }
 
diff --git a/common/partitions/efi.c b/common/partitions/efi.c
index 59b9fa9b55..e58f86cd9d 100644
--- a/common/partitions/efi.c
+++ b/common/partitions/efi.c
@@ -636,6 +636,9 @@ static struct partition_desc *efi_partition(void *buf, struct block_device *blk)
 	gpt = epd->gpt;
 	ptes = epd->ptes;
 
+	epd->pd.first_usable_lba = le64_to_cpu(gpt->first_usable_lba);
+	epd->pd.last_usable_lba = le64_to_cpu(gpt->last_usable_lba);
+
 	blk->cdev.flags |= DEVFS_IS_GPT_PARTITIONED;
 
 	nb_part = le32_to_cpu(gpt->num_partition_entries);
@@ -713,6 +716,9 @@ static __maybe_unused struct partition_desc *efi_partition_create_table(struct b
 	gpt->alternate_lba = cpu_to_le64(last_lba(blk));
 	gpt->first_usable_lba = cpu_to_le64(first_usable_lba);
 	gpt->last_usable_lba = cpu_to_le64(last_lba(blk) - (gpt_size + 2));;
+
+	epd->pd.first_usable_lba = le64_to_cpu(gpt->first_usable_lba);
+	epd->pd.last_usable_lba = le64_to_cpu(gpt->last_usable_lba);
 	generate_random_guid((unsigned char *)&gpt->disk_guid);
 	gpt->partition_entry_lba = cpu_to_le64(first_usable_lba - gpt_size);
 	gpt->num_partition_entries = cpu_to_le32(num_partition_entries);
diff --git a/include/partitions.h b/include/partitions.h
index f73d028e29..3376731334 100644
--- a/include/partitions.h
+++ b/include/partitions.h
@@ -38,6 +38,8 @@ struct partition_desc {
 	struct list_head partitions;
 	struct partition_parser *parser;
 	struct block_device *blk;
+	uint64_t first_usable_lba;
+	uint64_t last_usable_lba;
 };
 
 struct partition_parser {

-- 
2.47.3




More information about the barebox mailing list