diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index cafc3aa3e7..9249dc24a1 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -6,6 +6,7 @@ #include "proto/block-io.h" #include "proto/device-path.h" #include "proto/disk-io.h" +#include "string-util-fundamental.h" #include "util.h" typedef struct { @@ -319,6 +320,132 @@ EFI_STATUS partition_open(const EFI_GUID *type, EFI_HANDLE *device, EFI_HANDLE * return EFI_SUCCESS; } +static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { + EFI_STATUS err; + + assert(dp); + + /* When booting from a CD-ROM via El Torito, the device path contains a CDROM node instead of + * a HARDDRIVE node. The CDROM node doesn't carry a partition UUID, so we need to read the GPT + * from the underlying disk to find it. */ + + const CDROM_DEVICE_PATH *cdrom = NULL; + for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) + if (node->Type == MEDIA_DEVICE_PATH && node->SubType == MEDIA_CDROM_DP) + cdrom = (const CDROM_DEVICE_PATH *) node; + if (!cdrom) { + log_debug("No CDROM device path node found."); + return NULL; + } + + /* Chop off the CDROM node to get the whole-disk device path */ + _cleanup_free_ EFI_DEVICE_PATH *disk_path = device_path_replace_node(dp, &cdrom->Header, /* new_node= */ NULL); + + EFI_DEVICE_PATH *remaining = disk_path; + EFI_HANDLE disk_handle; + err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &remaining, &disk_handle); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to locate disk device for CDROM: %m"); + return NULL; + } + + (void) BS->ConnectController(disk_handle, /* DriverImageHandle= */ NULL, /* RemainingDevicePath= */ NULL, /* Recursive= */ true); + + EFI_BLOCK_IO_PROTOCOL *block_io; + err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to get block I/O protocol for CDROM disk: %m"); + return NULL; + } + + if (block_io->Media->LogicalPartition || !block_io->Media->MediaPresent || + block_io->Media->LastBlock <= 1) { + log_debug("CDROM disk has unsuitable media (partition=%ls, present=%ls, lastblock=%" PRIu64 ").", + yes_no(block_io->Media->LogicalPartition), + yes_no(block_io->Media->MediaPresent), + (uint64_t) block_io->Media->LastBlock); + return NULL; + } + + uint32_t iso9660_block_size = block_io->Media->BlockSize; + if (iso9660_block_size < 512 || iso9660_block_size > 4096 || !ISPOWEROF2(iso9660_block_size)) { + log_debug("Unexpected CDROM block size %" PRIu32 ", skipping.", iso9660_block_size); + return NULL; + } + + EFI_DISK_IO_PROTOCOL *disk_io; + err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_DISK_IO_PROTOCOL), (void **) &disk_io); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to get disk I/O protocol for CDROM disk: %m"); + return NULL; + } + + uint32_t media_id = block_io->Media->MediaId; + + /* Probe for the GPT header at multiple possible sector sizes (512, 1024, 2048, 4096). + * The GPT header is at LBA 1, i.e. byte offset == sector_size. On CD-ROMs, the GPT + * may use a different sector size than the media's block size (e.g. 512-byte GPT sectors + * on 2048-byte CD-ROM blocks), so we try all possibilities. If the primary GPT header is + * corrupt but contains a valid backup LBA, fall back to the backup header. */ + uint32_t gpt_sector_size = 0; + GptHeader gpt; + _cleanup_free_ void *entries = NULL; + for (uint32_t ss = 512; ss <= 4096; ss <<= 1) { + EFI_LBA backup_lba = 0; + + err = read_gpt_entries(disk_io, media_id, ss, /* lba= */ 1, &backup_lba, &gpt, &entries); + if (err == EFI_SUCCESS) { + gpt_sector_size = ss; + break; + } + if (err != EFI_NOT_FOUND) + log_debug_status(err, "Failed to read primary GPT header at sector size %"PRIu32", ignoring: %m", ss); + + if (backup_lba != 0) { + err = read_gpt_entries(disk_io, media_id, ss, backup_lba, /* reterr_backup_lba= */ NULL, &gpt, &entries); + if (err == EFI_SUCCESS) { + gpt_sector_size = ss; + break; + } + if (err != EFI_NOT_FOUND) + log_debug_status(err, "Failed to read backup GPT header at sector size %"PRIu32", ignoring: %m", ss); + } + } + + if (gpt_sector_size == 0) { + log_debug("No valid GPT found on CDROM at any sector size."); + return NULL; + } + + log_debug("Found GPT on CDROM with sector size %" PRIu32 ", %" PRIu32 " partition entries.", + gpt_sector_size, gpt.NumberOfPartitionEntries); + + /* Find the partition whose byte offset matches the CDROM's PartitionStart. + * CDROM PartitionStart is in media iso9660_block_size units, GPT StartingLBA is in gpt_sector_size units. */ + uint64_t cdrom_start; + if (!MUL_SAFE(&cdrom_start, cdrom->PartitionStart, iso9660_block_size)) { + log_debug("CDROM start offset overflow."); + return NULL; + } + + for (size_t i = 0; i < gpt.NumberOfPartitionEntries; i++) { + const EFI_PARTITION_ENTRY *entry = + (const EFI_PARTITION_ENTRY *) ((const uint8_t *) entries + gpt.SizeOfPartitionEntry * i); + + if (!efi_guid_equal(&entry->PartitionTypeGUID, &(const EFI_GUID) ESP_GUID)) + continue; + + uint64_t entry_start; + if (MUL_SAFE(&entry_start, entry->StartingLBA, gpt_sector_size) && + entry_start == cdrom_start) + return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(entry->UniquePartitionGUID)); + } + + log_debug("No ESP partition matches CDROM start offset %" PRIu64 " (block size %" PRIu32 ").", + cdrom->PartitionStart, iso9660_block_size); + return NULL; +} + char16_t *disk_get_part_uuid(EFI_HANDLE *handle) { EFI_STATUS err; EFI_DEVICE_PATH *dp; @@ -332,16 +459,17 @@ char16_t *disk_get_part_uuid(EFI_HANDLE *handle) { if (err != EFI_SUCCESS) return NULL; - for (; !device_path_is_end(dp); dp = device_path_next_node(dp)) { - if (dp->Type != MEDIA_DEVICE_PATH || dp->SubType != MEDIA_HARDDRIVE_DP) + for (EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) { + if (node->Type != MEDIA_DEVICE_PATH || node->SubType != MEDIA_HARDDRIVE_DP) continue; - HARDDRIVE_DEVICE_PATH *hd = (HARDDRIVE_DEVICE_PATH *) dp; + HARDDRIVE_DEVICE_PATH *hd = (HARDDRIVE_DEVICE_PATH *) node; if (hd->SignatureType != SIGNATURE_TYPE_GUID) continue; return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(hd->SignatureGuid)); } - return NULL; + /* No GPT partition node found — try CDROM device path as fallback */ + return disk_get_part_uuid_cdrom(dp); } diff --git a/src/boot/proto/device-path.h b/src/boot/proto/device-path.h index d81c0e1f8d..658c482df5 100644 --- a/src/boot/proto/device-path.h +++ b/src/boot/proto/device-path.h @@ -30,6 +30,7 @@ enum { ACPI_DP = 0x01, MEDIA_HARDDRIVE_DP = 0x01, + MEDIA_CDROM_DP = 0x02, MEDIA_VENDOR_DP = 0x03, MEDIA_FILEPATH_DP = 0x04, MEDIA_PIWG_FW_FILE_DP = 0x06, @@ -84,6 +85,14 @@ typedef struct { uint8_t SignatureType; } _packed_ HARDDRIVE_DEVICE_PATH; +typedef struct { + EFI_DEVICE_PATH Header; + uint32_t BootEntry; + uint64_t PartitionStart; /* In media block size units */ + uint64_t PartitionSize; /* In media block size units */ +} _packed_ CDROM_DEVICE_PATH; +assert_cc(sizeof(CDROM_DEVICE_PATH) == 24); + typedef struct { EFI_DEVICE_PATH Header; char16_t PathName[];