shared/gpt: add gpt_probe() for GPT header and partition entry reading
Add gpt_probe() which probes for a GPT partition table at various sector sizes (512-4096) and optionally returns the header and partition entries. Returns the detected sector size on success, 0 if no GPT was found, or negative errno on error. Refactor probe_sector_size() in dissect-image.c to be a thin wrapper around gpt_probe(). Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
Daan De Meyer
parent
08eff23a2d
commit
0e187fae07
+13
-43
@@ -139,54 +139,24 @@ static const char *getenv_fstype(PartitionDesignator d) {
|
||||
|
||||
int probe_sector_size(int fd, uint32_t *ret) {
|
||||
|
||||
/* Disk images might be for 512B or for 4096 sector sizes, let's try to auto-detect that by searching
|
||||
* for the GPT headers at the relevant byte offsets */
|
||||
|
||||
assert_cc(sizeof(GptHeader) == 92);
|
||||
|
||||
/* We expect a sector size in the range 512…4096. The GPT header is located in the second
|
||||
* sector. Hence it could be at byte 512 at the earliest, and at byte 4096 at the latest. And we must
|
||||
* read with granularity of the largest sector size we care about. Which means 8K. */
|
||||
uint8_t sectors[2 * 4096];
|
||||
uint32_t found = 0;
|
||||
ssize_t n;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ret);
|
||||
|
||||
n = pread(fd, sectors, sizeof(sectors), 0);
|
||||
if (n < 0)
|
||||
return -errno;
|
||||
if (n != sizeof(sectors)) /* too short? */
|
||||
goto not_found;
|
||||
|
||||
/* Let's see if we find the GPT partition header with various expected sector sizes */
|
||||
for (uint32_t sz = 512; sz <= 4096; sz <<= 1) {
|
||||
const GptHeader *p;
|
||||
|
||||
assert(sizeof(sectors) >= sz * 2);
|
||||
p = (const GptHeader*) (sectors + sz);
|
||||
|
||||
if (!gpt_header_has_signature(p))
|
||||
continue;
|
||||
|
||||
if (found != 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
|
||||
"Detected valid partition table at offsets matching multiple sector sizes, refusing.");
|
||||
|
||||
found = sz;
|
||||
ssize_t ssz = gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL);
|
||||
if (ssz == -ENOTUNIQ)
|
||||
return log_debug_errno(ssz,
|
||||
"Detected valid partition table at offsets matching multiple sector sizes, refusing.");
|
||||
if (ssz < 0)
|
||||
return ssz;
|
||||
if (ssz == 0) {
|
||||
log_debug("Couldn't find any partition table to derive sector size of.");
|
||||
*ret = 512; /* pick the traditional default */
|
||||
return 0; /* indicate we didn't find it */
|
||||
}
|
||||
|
||||
if (found != 0) {
|
||||
log_debug("Determined sector size %" PRIu32 " based on discovered partition table.", found);
|
||||
*ret = found;
|
||||
return 1; /* indicate we *did* find it */
|
||||
}
|
||||
|
||||
not_found:
|
||||
log_debug("Couldn't find any partition table to derive sector size of.");
|
||||
*ret = 512; /* pick the traditional default */
|
||||
return 0; /* indicate we didn't find it */
|
||||
log_debug("Determined sector size %" PRIu32 " based on discovered partition table.", (uint32_t) ssz);
|
||||
*ret = ssz;
|
||||
return 1; /* indicate we *did* find it */
|
||||
}
|
||||
|
||||
int probe_sector_size_prefer_ioctl(int fd, uint32_t *ret) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "gpt.h"
|
||||
#include "string-table.h"
|
||||
@@ -391,3 +393,86 @@ bool gpt_header_has_signature(const GptHeader *p) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t gpt_probe(
|
||||
int fd,
|
||||
GptHeader *ret_header,
|
||||
void **ret_entries,
|
||||
uint32_t *ret_n_entries,
|
||||
uint32_t *ret_entry_size) {
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
/* Disk images might be for 512B or for 4096 sector sizes, let's try to auto-detect that by searching
|
||||
* for the GPT headers at the relevant byte offsets. */
|
||||
|
||||
assert_cc(sizeof(GptHeader) == 92);
|
||||
|
||||
/* We expect a sector size in the range 512…4096. The GPT header is located in the second
|
||||
* sector. Hence it could be at byte 512 at the earliest, and at byte 4096 at the latest. And we must
|
||||
* read with granularity of the largest sector size we care about. Which means 8K. */
|
||||
uint8_t sectors[2 * 4096];
|
||||
|
||||
ssize_t n = pread(fd, sectors, sizeof(sectors), 0);
|
||||
if (n < 0)
|
||||
return -errno;
|
||||
if ((size_t) n < sizeof(sectors))
|
||||
return 0; /* too short */
|
||||
|
||||
/* Let's see if we find the GPT partition header with various expected sector sizes */
|
||||
uint32_t found = 0;
|
||||
for (uint32_t sz = 512; sz <= 4096; sz <<= 1) {
|
||||
const GptHeader *p = (const GptHeader *) (sectors + sz);
|
||||
|
||||
if (!gpt_header_has_signature(p))
|
||||
continue;
|
||||
|
||||
if (found != 0)
|
||||
return -ENOTUNIQ;
|
||||
|
||||
found = sz;
|
||||
}
|
||||
|
||||
if (found == 0)
|
||||
return 0;
|
||||
|
||||
const GptHeader *h = (const GptHeader *) (sectors + found);
|
||||
|
||||
uint32_t entry_sz = le32toh(h->size_of_partition_entry);
|
||||
uint32_t entry_count = le32toh(h->number_of_partition_entries);
|
||||
|
||||
if (ret_entries) {
|
||||
uint64_t entry_lba = le64toh(h->partition_entry_lba);
|
||||
if (entry_lba > (uint64_t) INT64_MAX / found)
|
||||
return -EBADMSG;
|
||||
|
||||
uint64_t entry_offset = entry_lba * found;
|
||||
|
||||
if (entry_sz < sizeof(GptPartitionEntry) || entry_count == 0 || entry_count > 1024)
|
||||
return -EBADMSG;
|
||||
if (entry_sz > SIZE_MAX / entry_count)
|
||||
return -EBADMSG;
|
||||
|
||||
size_t entries_size = (size_t) entry_sz * entry_count;
|
||||
_cleanup_free_ void *entries = malloc(entries_size);
|
||||
if (!entries)
|
||||
return -ENOMEM;
|
||||
|
||||
n = pread(fd, entries, entries_size, entry_offset);
|
||||
if (n < 0)
|
||||
return -errno;
|
||||
if ((size_t) n < entries_size)
|
||||
return -EBADMSG;
|
||||
|
||||
*ret_entries = TAKE_PTR(entries);
|
||||
}
|
||||
|
||||
if (ret_header)
|
||||
*ret_header = *h;
|
||||
if (ret_n_entries)
|
||||
*ret_n_entries = entry_count;
|
||||
if (ret_entry_size)
|
||||
*ret_entry_size = entry_sz;
|
||||
|
||||
return found; /* sector size */
|
||||
}
|
||||
|
||||
@@ -113,3 +113,10 @@ typedef struct {
|
||||
} _packed_ GptHeader;
|
||||
|
||||
bool gpt_header_has_signature(const GptHeader *p) _pure_;
|
||||
|
||||
ssize_t gpt_probe(
|
||||
int fd,
|
||||
GptHeader *ret_header,
|
||||
void **ret_entries,
|
||||
uint32_t *ret_n_entries,
|
||||
uint32_t *ret_entry_size);
|
||||
|
||||
+152
-7
@@ -1,8 +1,13 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "architecture.h"
|
||||
#include "fd-util.h"
|
||||
#include "gpt.h"
|
||||
#include "log.h"
|
||||
#include "memfd-util.h"
|
||||
#include "memory-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "strv.h"
|
||||
#include "tests.h"
|
||||
@@ -49,19 +54,19 @@ TEST(verity_mappings) {
|
||||
PartitionDesignator q;
|
||||
|
||||
q = partition_verity_hash_of(p);
|
||||
assert_se(q < 0 || partition_verity_hash_to_data(q) == p);
|
||||
ASSERT_TRUE(q < 0 || partition_verity_hash_to_data(q) == p);
|
||||
|
||||
q = partition_verity_sig_of(p);
|
||||
assert_se(q < 0 || partition_verity_sig_to_data(q) == p);
|
||||
ASSERT_TRUE(q < 0 || partition_verity_sig_to_data(q) == p);
|
||||
|
||||
q = partition_verity_hash_to_data(p);
|
||||
assert_se(q < 0 || partition_verity_hash_of(q) == p);
|
||||
ASSERT_TRUE(q < 0 || partition_verity_hash_of(q) == p);
|
||||
|
||||
q = partition_verity_sig_to_data(p);
|
||||
assert_se(q < 0 || partition_verity_sig_of(q) == p);
|
||||
ASSERT_TRUE(q < 0 || partition_verity_sig_of(q) == p);
|
||||
|
||||
q = partition_verity_to_data(p);
|
||||
assert_se(q < 0 || partition_verity_hash_of(q) == p || partition_verity_sig_of(q) == p);
|
||||
ASSERT_TRUE(q < 0 || partition_verity_hash_of(q) == p || partition_verity_sig_of(q) == p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +99,7 @@ TEST(override_architecture) {
|
||||
x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64);
|
||||
ASSERT_EQ(x.arch, y.arch);
|
||||
ASSERT_EQ(x.designator, y.designator);
|
||||
assert_se(sd_id128_equal(x.uuid, y.uuid));
|
||||
ASSERT_EQ_ID128(x.uuid, y.uuid);
|
||||
ASSERT_STREQ(x.name, y.name);
|
||||
|
||||
/* If the partition type does not have an architecture, nothing should change. */
|
||||
@@ -105,8 +110,148 @@ TEST(override_architecture) {
|
||||
x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64);
|
||||
ASSERT_EQ(x.arch, y.arch);
|
||||
ASSERT_EQ(x.designator, y.designator);
|
||||
assert_se(sd_id128_equal(x.uuid, y.uuid));
|
||||
ASSERT_EQ_ID128(x.uuid, y.uuid);
|
||||
ASSERT_STREQ(x.name, y.name);
|
||||
}
|
||||
|
||||
static void make_gpt(int fd, uint32_t sector_size, const GptPartitionEntry *part_entries, size_t n_entries) {
|
||||
/* Zero-fill enough for header probing (gpt_probe reads 2*4096 = 8KB) */
|
||||
static const uint8_t zeros[2 * 4096] = {};
|
||||
ASSERT_OK_EQ_ERRNO(pwrite(fd, zeros, sizeof(zeros), 0), (ssize_t) sizeof(zeros));
|
||||
|
||||
GptHeader h = {
|
||||
.signature = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' },
|
||||
.revision = htole32(UINT32_C(0x00010000)),
|
||||
.header_size = htole32(sizeof(GptHeader)),
|
||||
.my_lba = htole64(1),
|
||||
.partition_entry_lba = htole64(2),
|
||||
.number_of_partition_entries = htole32(n_entries),
|
||||
.size_of_partition_entry = htole32(sizeof(GptPartitionEntry)),
|
||||
};
|
||||
ASSERT_OK_EQ_ERRNO(pwrite(fd, &h, sizeof(h), sector_size), (ssize_t) sizeof(h));
|
||||
|
||||
if (n_entries > 0) {
|
||||
size_t entries_size = n_entries * sizeof(GptPartitionEntry);
|
||||
ASSERT_OK_EQ_ERRNO(pwrite(fd, part_entries, entries_size, 2 * sector_size), (ssize_t) entries_size);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(gpt_probe_empty) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
|
||||
fd = ASSERT_OK(memfd_new("test-gpt-probe"));
|
||||
ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0);
|
||||
}
|
||||
|
||||
TEST(gpt_probe_too_short) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
static const uint8_t buf[4096] = {};
|
||||
|
||||
fd = ASSERT_OK(memfd_new("test-gpt-probe"));
|
||||
ASSERT_OK_EQ_ERRNO(pwrite(fd, buf, sizeof(buf), 0), (ssize_t) sizeof(buf));
|
||||
ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0);
|
||||
}
|
||||
|
||||
TEST(gpt_probe_no_signature) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
static const uint8_t buf[2 * 4096] = {};
|
||||
|
||||
fd = ASSERT_OK(memfd_new("test-gpt-probe"));
|
||||
ASSERT_OK_EQ_ERRNO(pwrite(fd, buf, sizeof(buf), 0), (ssize_t) sizeof(buf));
|
||||
ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0);
|
||||
}
|
||||
|
||||
TEST(gpt_probe_sector_512) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
|
||||
const GptPartitionEntry entries[2] = {
|
||||
{
|
||||
.unique_partition_guid = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 },
|
||||
.starting_lba = htole64(100),
|
||||
.ending_lba = htole64(200),
|
||||
},
|
||||
{
|
||||
.unique_partition_guid = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20 },
|
||||
.starting_lba = htole64(300),
|
||||
.ending_lba = htole64(400),
|
||||
},
|
||||
};
|
||||
|
||||
fd = ASSERT_OK(memfd_new("test-gpt-probe"));
|
||||
make_gpt(fd, 512, entries, 2);
|
||||
|
||||
/* Sector size detection only */
|
||||
ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 512);
|
||||
|
||||
/* Header return */
|
||||
GptHeader h;
|
||||
ASSERT_OK_EQ(gpt_probe(fd, &h, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 512);
|
||||
ASSERT_EQ(le32toh(h.number_of_partition_entries), 2u);
|
||||
ASSERT_EQ(le32toh(h.size_of_partition_entry), (uint32_t) sizeof(GptPartitionEntry));
|
||||
|
||||
/* Full probe with entries */
|
||||
_cleanup_free_ void *ret_entries = NULL;
|
||||
uint32_t n, sz;
|
||||
ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, &ret_entries, &n, &sz), (ssize_t) 512);
|
||||
ASSERT_EQ(n, 2u);
|
||||
ASSERT_EQ(sz, (uint32_t) sizeof(GptPartitionEntry));
|
||||
ASSERT_NOT_NULL(ret_entries);
|
||||
|
||||
GptPartitionEntry *e = ret_entries;
|
||||
ASSERT_EQ(memcmp_nn(e[0].unique_partition_guid, sizeof(e[0].unique_partition_guid), entries[0].unique_partition_guid, sizeof(entries[0].unique_partition_guid)), 0);
|
||||
ASSERT_EQ(memcmp_nn(e[1].unique_partition_guid, sizeof(e[1].unique_partition_guid), entries[1].unique_partition_guid, sizeof(entries[1].unique_partition_guid)), 0);
|
||||
ASSERT_EQ(le64toh(e[0].starting_lba), UINT64_C(100));
|
||||
ASSERT_EQ(le64toh(e[1].starting_lba), UINT64_C(300));
|
||||
}
|
||||
|
||||
TEST(gpt_probe_sector_4096) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
|
||||
const GptPartitionEntry entry = {
|
||||
.unique_partition_guid = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11,
|
||||
0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 },
|
||||
.starting_lba = htole64(50),
|
||||
.ending_lba = htole64(100),
|
||||
};
|
||||
|
||||
fd = ASSERT_OK(memfd_new("test-gpt-probe"));
|
||||
make_gpt(fd, 4096, &entry, 1);
|
||||
|
||||
ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 4096);
|
||||
|
||||
_cleanup_free_ void *ret_entries = NULL;
|
||||
uint32_t n, sz;
|
||||
ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, &ret_entries, &n, &sz), (ssize_t) 4096);
|
||||
ASSERT_EQ(n, 1u);
|
||||
|
||||
GptPartitionEntry *e = ret_entries;
|
||||
ASSERT_EQ(memcmp_nn(e[0].unique_partition_guid, sizeof(e[0].unique_partition_guid), entry.unique_partition_guid, sizeof(entry.unique_partition_guid)), 0);
|
||||
ASSERT_EQ(le64toh(e[0].starting_lba), UINT64_C(50));
|
||||
}
|
||||
|
||||
TEST(gpt_probe_ambiguous) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
|
||||
const GptPartitionEntry entry = {};
|
||||
|
||||
fd = ASSERT_OK(memfd_new("test-gpt-probe"));
|
||||
make_gpt(fd, 512, &entry, 1);
|
||||
|
||||
/* Place a second valid header at offset 4096 */
|
||||
GptHeader h2 = {
|
||||
.signature = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' },
|
||||
.revision = htole32(UINT32_C(0x00010000)),
|
||||
.header_size = htole32(sizeof(GptHeader)),
|
||||
.my_lba = htole64(1),
|
||||
.partition_entry_lba = htole64(2),
|
||||
.number_of_partition_entries = htole32(1),
|
||||
.size_of_partition_entry = htole32(sizeof(GptPartitionEntry)),
|
||||
};
|
||||
ASSERT_OK_EQ_ERRNO(pwrite(fd, &h2, sizeof(h2), 4096), (ssize_t) sizeof(h2));
|
||||
|
||||
ASSERT_ERROR(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), ENOTUNIQ);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
|
||||
Reference in New Issue
Block a user