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:
Daan De Meyer
2026-04-02 13:59:55 +02:00
committed by Daan De Meyer
parent 08eff23a2d
commit 0e187fae07
4 changed files with 257 additions and 50 deletions
+13 -43
View File
@@ -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) {
+85
View File
@@ -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 */
}
+7
View File
@@ -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
View File
@@ -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);