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 <[email protected]>
---
 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


Reply via email to