From 688f011f317492225ded10b51edc19b39d62fbd4 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Mon, 26 May 2025 20:50:33 +0100 Subject: [PATCH] [iso] improve UEFI bootloader reporting * Add systemd-boot version reporting and fix GRUB version detection for CentOS * Add notes about the various Secure Boot gotchas (log only) * Enable download of remote active/revoked Secure Boot certificate thumbprints * Also rename the ceiling/floor align macros --- ChangeLog.txt | 12 +++++ src/db.h | 16 +++---- src/drive.c | 24 +++++----- src/format.c | 4 +- src/hash.c | 102 +++++++++++++++++++++++++++++-------------- src/iso.c | 48 +++++++++++++++++++- src/missing.h | 8 +++- src/parser.c | 81 +++++++++++++++++++++++++++++----- src/rufus.c | 119 ++++++++++++++++++++++++-------------------------- src/rufus.h | 21 ++++++--- src/rufus.rc | 10 ++--- src/stdlg.c | 26 +++++++++-- 12 files changed, 325 insertions(+), 146 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 9cad6c6e..d9ab4e03 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,15 @@ +o Version 4.8 (2025.06.??) + Switch to wimlib for all WIM image processing: + - Greatly speeds up image analysis when opening Windows ISOs + - Can speed up Windows To Go drive creation (But won't do miracles if you have a crap drive) + - Might help with Parallels limitations on Mac (But Rufus on Parallels is still UNSUPPORTED) + - Enables the splitting of >4GB files with Alt-E (But still WAY SLOWER than using UEFI:NTFS) + Switch to using Visual Studio binaries everywhere, due to MinGW DLL delay-loading limitations + Add more exceptions for Linux ISOs that restrict themselves to DD mode (Nobara, openSUSE, ...) + Improve reporting of UEFI bootloaders in the log, with info on the Secure Boot status + Fix an issue with size limitations when writing an uncompressed VHD back to the same drive + Fix a crash when opening the log with the 32-bit MinGW compiled version + o Version 4.7 (2025.04.09) Add a mechanism to detect and download updated DBXs from the official UEFI repository Add ztsd compression support for disk images diff --git a/src/db.h b/src/db.h index b3db434c..1b5a872b 100644 --- a/src/db.h +++ b/src/db.h @@ -127,24 +127,22 @@ static uint8_t sha256db[] = { * Contains the SHA-1 thumbprints of the issuer certificate of the official * Secure Boot signing authority (i.e. Microsoft). */ -static uint8_t certauth[] = { +static const char db_sb_active_txt[] = // 'Microsoft Windows Production PCA 2011' - 0x58, 0x0a, 0x6f, 0x4c, 0xc4, 0xe4, 0xb6, 0x69, 0xb9, 0xeb, 0xdc, 0x1b, 0x2b, 0x3e, 0x08, 0x7b, 0x80, 0xd0, 0x67, 0x8d, + "580a6f4cc4e4b669b9ebdc1b2b3e087b80d0678d\n" // 'Microsoft Corporation UEFI CA 2011' - 0x46, 0xde, 0xf6, 0x3b, 0x5c, 0xe6, 0x1c, 0xf8, 0xba, 0x0d, 0xe2, 0xe6, 0x63, 0x9c, 0x10, 0x19, 0xd0, 0xed, 0x14, 0xf3, + "46def63b5ce61cf8ba0de2e6639c1019d0ed14f3\n" // 'Windows UEFI CA 2023' - 0x45, 0xa0, 0xfa, 0x32, 0x60, 0x47, 0x73, 0xc8, 0x24, 0x33, 0xc3, 0xb7, 0xd5, 0x9e, 0x74, 0x66, 0xb3, 0xac, 0x0c, 0x67, + "45a0fa32604773c82433c3b7d59e7466b3ac0c67\n" // 'Microsoft UEFI CA 2023' - 0xb5, 0xee, 0xb4, 0xa6, 0x70, 0x60, 0x48, 0x07, 0x3f, 0x0e, 0xd2, 0x96, 0xe7, 0xf5, 0x80, 0xa7, 0x90, 0xb5, 0x9e, 0xaa, -}; + "b5eeb4a6706048073f0ed296e7f580a790b59eaa"; /* * Contains the SHA-1 thumbprints of certificates that are being revoked by DBX. * This only includes the 'Microsoft Windows Production PCA 2011' for now. */ -static uint8_t certdbx[] = { - 0x58, 0x0a, 0x6f, 0x4c, 0xc4, 0xe4, 0xb6, 0x69, 0xb9, 0xeb, 0xdc, 0x1b, 0x2b, 0x3e, 0x08, 0x7b, 0x80, 0xd0, 0x67, 0x8d, -}; +static const char db_sb_revoked_txt[] = + "580a6f4cc4e4b669b9ebdc1b2b3e087b80d0678d"; /* * Extended SBATLevel.txt that merges Linux SBAT with Microsoft's SVN diff --git a/src/drive.c b/src/drive.c index ab4a6d57..402aa53b 100644 --- a/src/drive.c +++ b/src/drive.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Drive access function calls - * Copyright © 2011-2024 Pete Batard + * Copyright © 2011-2025 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -2299,7 +2299,7 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m // CHS sizes that IBM imparted upon us. Long story short, we now align to a // cylinder size that is itself aligned to the cluster size. // If this actually breaks old systems, please send your complaints to IBM. - SelectedDrive.Partition[pi].Offset = HI_ALIGN_X_TO_Y(bytes_per_track, ClusterSize); + SelectedDrive.Partition[pi].Offset = CEILING_ALIGN(bytes_per_track, ClusterSize); // GRUB2 no longer fits in the usual 31½ KB that the above computation provides // so just unconditionally double that size and get on with it. SelectedDrive.Partition[pi].Offset *= 2; @@ -2316,9 +2316,9 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m SelectedDrive.Partition[pi].Size = esp_size; SelectedDrive.Partition[pi + 1].Offset = SelectedDrive.Partition[pi].Offset + SelectedDrive.Partition[pi].Size; // Align next partition to track and cluster - SelectedDrive.Partition[pi + 1].Offset = HI_ALIGN_X_TO_Y(SelectedDrive.Partition[pi + 1].Offset, bytes_per_track); + SelectedDrive.Partition[pi + 1].Offset = CEILING_ALIGN(SelectedDrive.Partition[pi + 1].Offset, bytes_per_track); if (ClusterSize % SelectedDrive.SectorSize == 0) - SelectedDrive.Partition[pi + 1].Offset = LO_ALIGN_X_TO_Y(SelectedDrive.Partition[pi + 1].Offset, ClusterSize); + SelectedDrive.Partition[pi + 1].Offset = FLOOR_ALIGN(SelectedDrive.Partition[pi + 1].Offset, ClusterSize); assert(SelectedDrive.Partition[pi + 1].Offset >= SelectedDrive.Partition[pi].Offset + SelectedDrive.Partition[pi].Size); pi++; // Clear the extra partition we processed @@ -2331,9 +2331,9 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m wcscpy(SelectedDrive.Partition[pi].Name, L"Microsoft Reserved Partition"); SelectedDrive.Partition[pi].Size = 128 * MB; SelectedDrive.Partition[pi + 1].Offset = SelectedDrive.Partition[pi].Offset + SelectedDrive.Partition[pi].Size; - SelectedDrive.Partition[pi + 1].Offset = HI_ALIGN_X_TO_Y(SelectedDrive.Partition[pi + 1].Offset, bytes_per_track); + SelectedDrive.Partition[pi + 1].Offset = CEILING_ALIGN(SelectedDrive.Partition[pi + 1].Offset, bytes_per_track); if (ClusterSize % SelectedDrive.SectorSize == 0) - SelectedDrive.Partition[pi + 1].Offset = LO_ALIGN_X_TO_Y(SelectedDrive.Partition[pi + 1].Offset, ClusterSize); + SelectedDrive.Partition[pi + 1].Offset = FLOOR_ALIGN(SelectedDrive.Partition[pi + 1].Offset, ClusterSize); assert(SelectedDrive.Partition[pi + 1].Offset >= SelectedDrive.Partition[pi].Offset + SelectedDrive.Partition[pi].Size); pi++; extra_partitions &= ~(XP_MSR); @@ -2352,16 +2352,16 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m assert(persistence_size != 0); partition_index[PI_CASPER] = pi; wcscpy(SelectedDrive.Partition[pi].Name, L"Linux Persistence"); - SelectedDrive.Partition[pi++].Size = HI_ALIGN_X_TO_Y(persistence_size, bytes_per_track); + SelectedDrive.Partition[pi++].Size = CEILING_ALIGN(persistence_size, bytes_per_track); } if (extra_partitions & XP_ESP) { partition_index[PI_ESP] = pi; wcscpy(SelectedDrive.Partition[pi].Name, L"EFI System Partition"); - SelectedDrive.Partition[pi++].Size = HI_ALIGN_X_TO_Y(esp_size, bytes_per_track); + SelectedDrive.Partition[pi++].Size = CEILING_ALIGN(esp_size, bytes_per_track); } else if (extra_partitions & XP_UEFI_NTFS) { partition_index[PI_UEFI_NTFS] = pi; wcscpy(SelectedDrive.Partition[pi].Name, L"UEFI:NTFS"); - SelectedDrive.Partition[pi++].Size = HI_ALIGN_X_TO_Y(uefi_ntfs_size, bytes_per_track); + SelectedDrive.Partition[pi++].Size = CEILING_ALIGN(uefi_ntfs_size, bytes_per_track); } else if (extra_partitions & XP_COMPAT) { wcscpy(SelectedDrive.Partition[pi].Name, L"BIOS Compatibility"); SelectedDrive.Partition[pi++].Size = bytes_per_track; // One track for the extra partition @@ -2377,13 +2377,13 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m last_offset -= 33ULL * SelectedDrive.SectorSize; for (i = pi - 1; i > mi; i--) { assert(SelectedDrive.Partition[i].Size < last_offset); - SelectedDrive.Partition[i].Offset = LO_ALIGN_X_TO_Y(last_offset - SelectedDrive.Partition[i].Size, bytes_per_track); + SelectedDrive.Partition[i].Offset = FLOOR_ALIGN(last_offset - SelectedDrive.Partition[i].Size, bytes_per_track); last_offset = SelectedDrive.Partition[i].Offset; } // With the above, Compute the main partition size (which we align to a track) assert(last_offset > SelectedDrive.Partition[mi].Offset); - SelectedDrive.Partition[mi].Size = LO_ALIGN_X_TO_Y(last_offset - SelectedDrive.Partition[mi].Offset, bytes_per_track); + SelectedDrive.Partition[mi].Size = FLOOR_ALIGN(last_offset - SelectedDrive.Partition[mi].Offset, bytes_per_track); // Try to make sure that the main partition size is a multiple of the cluster size // This can be especially important when trying to capture an NTFS partition as FFU, as, when // the NTFS partition is aligned to cluster size, the FFU capture parses the NTFS allocated @@ -2391,7 +2391,7 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m // a full sector by sector scan of the NTFS partition and records any non-zero garbage, which // may include garbage leftover data from a previous reformat... if (ClusterSize % SelectedDrive.SectorSize == 0) - SelectedDrive.Partition[mi].Size = LO_ALIGN_X_TO_Y(SelectedDrive.Partition[mi].Size, ClusterSize); + SelectedDrive.Partition[mi].Size = FLOOR_ALIGN(SelectedDrive.Partition[mi].Size, ClusterSize); if (SelectedDrive.Partition[mi].Size <= 0) { uprintf("Error: Invalid %S size", SelectedDrive.Partition[mi].Name); return FALSE; diff --git a/src/format.c b/src/format.c index a11a4a43..4fac3f2f 100644 --- a/src/format.c +++ b/src/format.c @@ -1372,9 +1372,9 @@ static BOOL WriteDrive(HANDLE hPhysicalDrive, BOOL bZeroDrive) // 2. WriteFile fails unless the size is a multiple of sector size if (read_size[read_bufnum] % SelectedDrive.SectorSize != 0) { - if_not_assert(HI_ALIGN_X_TO_Y(read_size[read_bufnum], SelectedDrive.SectorSize) <= buf_size) + if_not_assert(CEILING_ALIGN(read_size[read_bufnum], SelectedDrive.SectorSize) <= buf_size) goto out; - read_size[read_bufnum] = HI_ALIGN_X_TO_Y(read_size[read_bufnum], SelectedDrive.SectorSize); + read_size[read_bufnum] = CEILING_ALIGN(read_size[read_bufnum], SelectedDrive.SectorSize); } // 3. Switch to the next reading buffer diff --git a/src/hash.c b/src/hash.c index 663b58d2..4e611a11 100644 --- a/src/hash.c +++ b/src/hash.c @@ -114,7 +114,7 @@ StrArray modified_files = { 0 }; extern int default_thread_priority; extern const char* efi_archname[ARCH_MAX]; -extern char* sbat_level_txt; +extern char *sbat_level_txt, *sb_active_txt, *sb_revoked_txt; extern BOOL expert_mode, usb_debug; /* @@ -2162,6 +2162,12 @@ static BOOL IsRevokedBySbat(uint8_t* buf, uint32_t len) uint32_t i, j, sbat_len; sbat_entry_t entry; + // Fall back to embedded sbat_level.txt if we couldn't access remote + if (sbat_entries == NULL) { + sbat_level_txt = safe_strdup(db_sbat_level_txt); + sbat_entries = GetSbatEntries(sbat_level_txt); + } + assert(sbat_entries != NULL); if (sbat_entries == NULL) return FALSE; @@ -2220,7 +2226,7 @@ static BOOL IsRevokedByDbx(uint8_t* hash, uint8_t* buf, uint32_t len) dbx_size = read_file(path, &dbx_data); needs_free = (dbx_data != NULL); if (needs_free) - duprintf("Using local %s for revocation check", path); + duprintf(" Using local %s for revocation check", path); } if (dbx_size == 0) { dbx_data = (BYTE*)GetResource(hMainInstance, MAKEINTRESOURCEA(IDR_DBX + i), @@ -2239,7 +2245,7 @@ static BOOL IsRevokedByDbx(uint8_t* hash, uint8_t* buf, uint32_t len) goto out; // Expect SHA-256 hashes if (!CompareGUID(&efi_sig_list->SignatureType, &EFI_CERT_SHA256_GUID)) { - uprintf("WARNING: %s is not using SHA-256 hashes - Cannot check for UEFI revocation!", dbx_name); + uprintf(" Warning: %s is not using SHA-256 hashes - Cannot check for UEFI revocation!", dbx_name); goto out; } fluff_size += efi_sig_list->SignatureHeaderSize; @@ -2301,7 +2307,7 @@ static BOOL IsRevokedBySvn(uint8_t* buf, uint32_t len) return TRUE; } } else { - uprintf("WARNING: Unexpected Secure Version Number size"); + uprintf(" Warning: Unexpected Secure Version Number size"); } } } @@ -2310,13 +2316,24 @@ static BOOL IsRevokedBySvn(uint8_t* buf, uint32_t len) static BOOL IsRevokedByCert(cert_info_t* info) { - int i; + uint32_t i; - for (i = 0; i < ARRAYSIZE(certdbx); i += SHA1_HASHSIZE) { - if (!expert_mode) - continue; - if (memcmp(info->thumbprint, &certdbx[i], SHA1_HASHSIZE) == 0) { - uprintf("Found '%s' revoked certificate", info->name); + // TODO: Enable this for non expert mode after enforcement of PCA2011 cert revocation + if (!expert_mode) + return FALSE; + + // Fall back to embedded Secure Boot thumbprints if we couldn't access remote + if (sb_revoked_certs == NULL) { + sb_revoked_txt = safe_strdup(db_sb_revoked_txt); + sb_revoked_certs = GetThumbprintEntries(sb_revoked_txt); + } + assert(sb_revoked_certs != NULL && sb_revoked_certs->count != 0); + if (sb_revoked_certs == NULL) + return FALSE; + + for (i = 0; i < sb_revoked_certs->count; i++) { + if (memcmp(info->thumbprint, sb_revoked_certs->list[i], SHA1_HASHSIZE) == 0) { + uuprintf(" Found '%s' revoked certificate", info->name); return TRUE; } } @@ -2325,7 +2342,7 @@ static BOOL IsRevokedByCert(cert_info_t* info) BOOL IsSignedBySecureBootAuthority(uint8_t* buf, uint32_t len) { - int i; + uint32_t i; uint8_t* cert; cert_info_t info; @@ -2337,8 +2354,19 @@ BOOL IsSignedBySecureBootAuthority(uint8_t* buf, uint32_t len) // Secure Boot Authority is always an issuer if (GetIssuerCertificateInfo(cert, &info) != 2) return FALSE; - for (i = 0; i < ARRAYSIZE(certauth); i += SHA1_HASHSIZE) { - if (memcmp(info.thumbprint, &certauth[i], SHA1_HASHSIZE) == 0) + + // Fall back to embedded Secure Boot thumbprints if we couldn't access remote + if (sb_active_certs == NULL) { + sb_active_txt = safe_strdup(db_sb_active_txt); + sb_active_certs = GetThumbprintEntries(sb_active_txt); + } + // If we still manage to get an empty list at this stage, I sure wanna know about it! + assert(sb_active_certs != NULL && sb_active_certs->count != 0); + if (sb_active_certs == NULL || sb_active_certs->count == 0) + return FALSE; + + for (i = 0; i < sb_active_certs->count; i++) { + if (memcmp(info.thumbprint, sb_active_certs->list[i], SHA1_HASHSIZE) == 0) return TRUE; } return FALSE; @@ -2352,13 +2380,7 @@ int IsBootloaderRevoked(uint8_t* buf, uint32_t len) IMAGE_NT_HEADERS32* pe_header; uint8_t* cert; cert_info_t info; - int r; - - // Fall back to embedded sbat_level.txt if we couldn't access remote - if (sbat_entries == NULL) { - sbat_level_txt = safe_strdup(db_sbat_level_txt); - sbat_entries = GetSbatEntries(sbat_level_txt); - } + int r, revoked = 0; if (buf == NULL || len < 0x100 || dos_header->e_magic != IMAGE_DOS_SIGNATURE) return -2; @@ -2370,29 +2392,45 @@ int IsBootloaderRevoked(uint8_t* buf, uint32_t len) cert = GetPeSignatureData(buf); r = GetIssuerCertificateInfo(cert, &info); if (r == 0) - uuprintf(" (Unsigned Bootloader)"); + uprintf(" (Unsigned Bootloader)"); else if (r > 0) - uuprintf(" Signed by: %s", info.name); + uprintf(" Signed by '%s'", info.name); if (!PE256Buffer(buf, len, hash)) return -1; // Check for UEFI DBX revocation if (IsRevokedByDbx(hash, buf, len)) - return 1; + revoked = 1; // Check for Microsoft SSP revocation - for (i = 0; i < pe256ssp_size * SHA256_HASHSIZE; i += SHA256_HASHSIZE) + for (i = 0; revoked == 0 && i < pe256ssp_size * SHA256_HASHSIZE; i += SHA256_HASHSIZE) if (memcmp(hash, &pe256ssp[i], SHA256_HASHSIZE) == 0) - return 2; + revoked = 2; // Check for Linux SBAT revocation - if (IsRevokedBySbat(buf, len)) - return 3; + if (revoked == 0 && IsRevokedBySbat(buf, len)) + revoked = 3; // Check for Microsoft SVN revocation - if (IsRevokedBySvn(buf, len)) - return 4; + if (revoked == 0 && IsRevokedBySvn(buf, len)) + revoked = 4; // Check for UEFI DBX certificate revocation - if (IsRevokedByCert(&info)) - return 5; - return 0; + if (revoked == 0 && IsRevokedByCert(&info)) + revoked = 5; + + // If signed and not revoked, print the various Secure Boot "gotchas" + if (r > 0 && revoked == 0) { + if (strcmp(info.name, "Microsoft Windows Production PCA 2011") == 0) { + uprintf(" Note: This bootloader may fail Secure Boot validation on systems that"); + uprintf(" have been updated to use the 'Windows UEFI CA 2023' certificate."); + } else if (strcmp(info.name, "Windows UEFI CA 2023") == 0) { + uprintf(" Note: This bootloader will fail Secure Boot validation on systems that"); + uprintf(" have not been updated to use the latest Secure Boot certificates"); + } else if (strcmp(info.name, "Microsoft Corporation UEFI CA 2011") == 0 || + strcmp(info.name, "Microsoft UEFI CA 2023") == 0) { + uprintf(" Note: This bootloader may fail Secure Boot validation on *some* systems,"); + uprintf(" unless you enable \"Microsoft 3rd-party UEFI CA\" in your 'BIOS'."); + } + } + + return revoked; } /* diff --git a/src/iso.c b/src/iso.c index 5b051865..a1996f5d 100644 --- a/src/iso.c +++ b/src/iso.c @@ -1055,8 +1055,12 @@ void GetGrubVersion(char* buf, size_t buf_size, const char* source) if (buf_size > max_string_size) { for (i = 0; i < buf_size - max_string_size; i++) { for (j = 0; j < ARRAYSIZE(grub_version_str); j++) { - if (memcmp(&buf[i], grub_version_str[j], strlen(grub_version_str[j]) + 1) == 0) + if (memcmp(&buf[i], grub_version_str[j], strlen(grub_version_str[j])) == 0) { + // For CentOS, who decided to add a '\n' after "GRUB version %s" + if (buf[i + strlen(grub_version_str[j]) + 1] == '\0') + i++; static_strcpy(grub_version, &buf[i + strlen(grub_version_str[j]) + 1]); + } } if (memcmp(&buf[i], grub_debug_is_enabled_str, strlen(grub_debug_is_enabled_str)) == 0) has_grub_debug_is_enabled = TRUE; @@ -1139,6 +1143,37 @@ void GetGrubFs(char* buf, size_t buf_size) } } +void GetEfiBootInfo(char* buf, size_t buf_size, const char* source) +{ + // Data to help us identify the EFI bootloader type + const struct { + const char* label; + const char* search_string; + } boot_info[] = { + { "Shim", "UEFI SHIM\n$Version: "}, + // NB: There's also an ID=systemd-boot\nVERSION="x.y.z" footer + // in the Arch systemd-boot EFI binary, but I'm not sure if we + // can count on this metadata footer to always be present... + { "systemd-boot", "#### LoaderInfo: systemd-boot " }, + }; + const size_t max_string_size = 64; + size_t i, j, k; + + if (buf_size > max_string_size) { + for (i = 0; i < buf_size - max_string_size; i++) { + for (j = 0; j < ARRAYSIZE(boot_info); j++) { + if (memcmp(&buf[i], boot_info[j].search_string, strlen(boot_info[j].search_string)) == 0) { + i += strlen(boot_info[j].search_string); + for (k = 0; k < 32 && i + k < buf_size - 1 && !isspace(buf[i + k]); k++); + buf[i + k] = '\0'; + uprintf(" Detected %s version: %s (from '%s')", boot_info[j].label, &buf[i], source); + return; + } + } + } + } +} + BOOL ExtractISO(const char* src_iso, const char* dest_dir, BOOL scan) { const char* basedir[] = { "i386", "amd64", "minint" }; @@ -1386,8 +1421,9 @@ out: } } for (j = 0; j < ARRAYSIZE(img_report.efi_boot_entry); j++) { + if (!img_report.efi_boot_entry[j].path[0]) + continue; if (img_report.efi_boot_entry[j].type == EBT_GRUB) { - size = (size_t)ReadISOFileToBuffer(src_iso, img_report.efi_boot_entry[j].path, &buf); if (size == 0) { uprintf(" Could not read Grub version from '%s'", img_report.efi_boot_entry[j].path); @@ -1397,6 +1433,14 @@ out: GetGrubFs(buf, size); } safe_free(buf); + } else if (img_report.efi_boot_entry[j].type == EBT_MAIN) { + size = (size_t)ReadISOFileToBuffer(src_iso, img_report.efi_boot_entry[j].path, &buf); + if (size == 0) { + uprintf(" Could not parse '%s'", img_report.efi_boot_entry[j].path); + } else { + GetEfiBootInfo(buf, size, img_report.efi_boot_entry[j].path); + } + safe_free(buf); } } for (i = 0; i < (int)grub_filesystems.Index; i++) { diff --git a/src/missing.h b/src/missing.h index a62203d3..62ce5c6e 100644 --- a/src/missing.h +++ b/src/missing.h @@ -30,10 +30,14 @@ #define MAX(a,b) (((a) > (b)) ? (a) : (b)) #endif -#define LO_ALIGN_X_TO_Y(x, y) (((x) / (y)) * (y)) -#define HI_ALIGN_X_TO_Y(x, y) ((((x) + (y) - 1) / (y)) * (y)) +#define MAP_BIT(bit) do { map[_log2(bit)] = b; b <<= 1; } while(0) + +#define FLOOR_ALIGN(x, y) (((x) / (y)) * (y)) +#define CEILING_ALIGN(x, y) ((((x) + (y) - 1) / (y)) * (y)) #define IS_HEXASCII(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'A' && (c) <= 'F') || ((c) >= 'a' && (c) <= 'f')) +#define FROM_HEXASCII(c) (((c) >= '0' && (c) <= '9') ? (c) - '0' : (((c) >= 'A' && (c) <= 'Z') ? (c) - 'A' + 10 : \ + (((c) >= 'a' && (c) <= 'z') ? (c) - 'a' + 10 : 0 ))) /* * Prefetch 64 bytes at address m, for read-only operation diff --git a/src/parser.c b/src/parser.c index 2f53b1d0..274a9705 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1582,20 +1582,18 @@ sbat_entry_t* GetSbatEntries(char* sbatlevel) BOOL eol, eof; char* version_str; uint32_t i, num_entries; - sbat_entry_t* _sbat_entries; + sbat_entry_t* sbat_list; if (sbatlevel == NULL) return NULL; - num_entries = 0; + num_entries = 1; for (i = 0; sbatlevel[i] != '\0'; i++) if (sbatlevel[i] == '\n') num_entries++; - if (num_entries == 0) - return NULL; - _sbat_entries = calloc(num_entries + 2, sizeof(sbat_entry_t)); - if (_sbat_entries == NULL) + sbat_list = calloc(num_entries + 1, sizeof(sbat_entry_t)); + if (sbat_list == NULL) return NULL; num_entries = 0; @@ -1611,7 +1609,7 @@ sbat_entry_t* GetSbatEntries(char* sbatlevel) i++; continue; } - _sbat_entries[num_entries].product = &sbatlevel[i]; + sbat_list[num_entries].product = &sbatlevel[i]; for (; sbatlevel[i] != ',' && sbatlevel[i] != '\0' && sbatlevel[i] != '\n'; i++); if (sbatlevel[i] == '\0' || sbatlevel[i] == '\n') break; @@ -1625,16 +1623,77 @@ sbat_entry_t* GetSbatEntries(char* sbatlevel) i++; // Allow the provision of an hex version if (version_str[0] == '0' && version_str[1] == 'x') - _sbat_entries[num_entries].version = strtoul(version_str, NULL, 16); + sbat_list[num_entries].version = strtoul(version_str, NULL, 16); else - _sbat_entries[num_entries].version = strtoul(version_str, NULL, 10); + sbat_list[num_entries].version = strtoul(version_str, NULL, 10); if (!eol) for (; sbatlevel[i] != '\0' && sbatlevel[i] != '\n'; i++); - if (_sbat_entries[num_entries].version != 0) + if (sbat_list[num_entries].version != 0) num_entries++; } + if (num_entries == 0) { + free(sbat_list); + return NULL; + } - return _sbat_entries; + return sbat_list; +} + +/* + * Parse a list of SHA-1 certificate hexascii thumbprints. + * List must be freed by the caller. + */ + +thumbprint_list_t* GetThumbprintEntries(char* thumbprints_txt) +{ + uint32_t i, j, num_entries; + thumbprint_list_t* thumbprints; + + if (thumbprints_txt == NULL) + return NULL; + + num_entries = 1; + for (i = 0; thumbprints_txt[i] != '\0'; i++) + if (thumbprints_txt[i] == '\n') + num_entries++; + + thumbprints = malloc(sizeof(thumbprint_list_t) + num_entries * SHA1_HASHSIZE); + if (thumbprints == NULL) + return NULL; + thumbprints->count = 0; + + for (i = 0; thumbprints_txt[i] != '\0'; ) { + // Eliminate blank lines + if (thumbprints_txt[i] == '\n') { + i++; + continue; + } + // Eliminate lines that don't start by an hexadecimal digit + if (!IS_HEXASCII(thumbprints_txt[i])) { + while (thumbprints_txt[i] != '\n' && thumbprints_txt[i] != '\0') + i++; + continue; + } + for (j = 0; thumbprints_txt[i] != '\n' && thumbprints_txt[i] != '\0'; i++, j++) { + if (!IS_HEXASCII(thumbprints_txt[i])) + break; + if ((j / 2) >= SHA1_HASHSIZE) + break; + thumbprints->list[thumbprints->count][j / 2] = thumbprints->list[thumbprints->count][j / 2] << 4; + thumbprints->list[thumbprints->count][j / 2] |= FROM_HEXASCII(thumbprints_txt[i]); + if (j == 2 * SHA1_HASHSIZE - 1) + thumbprints->count++; + } + while (thumbprints_txt[i] != '\n' && thumbprints_txt[i] != '\0') + i++; + } + + if (thumbprints->count == 0) { + free(thumbprints); + return NULL; + } + + return thumbprints; } /* diff --git a/src/rufus.c b/src/rufus.c index aa9fa163..5f557573 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -143,13 +143,14 @@ char embedded_sl_version_ext[2][32]; char ClusterSizeLabel[MAX_CLUSTER_SIZES][64]; char msgbox[1024], msgbox_title[32], *ini_file = NULL, *image_path = NULL, *short_image_path; char *archive_path = NULL, image_option_txt[128], *fido_url = NULL, *save_image_type = NULL; -char* sbat_level_txt = NULL; +char *sbat_level_txt = NULL, *sb_active_txt = NULL, *sb_revoked_txt = NULL; StrArray BlockingProcessList, ImageList; // Number of steps for each FS for FCC_STRUCTURE_PROGRESS const int nb_steps[FS_MAX] = { 5, 5, 12, 1, 10, 1, 1, 1, 1 }; const char* flash_type[BADLOCKS_PATTERN_TYPES] = { "SLC", "MLC", "TLC" }; RUFUS_DRIVE rufus_drive[MAX_DRIVES] = { 0 }; sbat_entry_t* sbat_entries = NULL; +thumbprint_list_t *sb_active_certs = NULL, *sb_revoked_certs = NULL; // TODO: Remember to update copyright year in stdlg's AboutCallback() WM_INITDIALOG, // localization_data.sh and the .rc when the year changes! @@ -1243,6 +1244,42 @@ out: return ret; } +void GetBootladerInfo(void) +{ + static const char* revocation_type[] = { "UEFI DBX", "Windows SSP", "Linux SBAT", "Windows SVN", "Cert DBX" }; + int r; + BOOL sb_signed; + uint32_t i, len; + uint8_t* buf = NULL; + + // Check UEFI bootloaders for revocation + if (!IS_EFI_BOOTABLE(img_report)) + return; + + assert(ARRAYSIZE(img_report.efi_boot_entry) > 0); + PrintStatus(0, MSG_351); + uprintf("UEFI bootloaders analysis:"); + for (i = 0; i < ARRAYSIZE(img_report.efi_boot_entry) && img_report.efi_boot_entry[i].path[0] != 0; i++) { + len = ReadISOFileToBuffer(image_path, img_report.efi_boot_entry[i].path, &buf); + if (len == 0) { + uprintf(" Warning: Failed to extract '%s' to check for UEFI Secure Boot info", img_report.efi_boot_entry[i].path); + continue; + } + sb_signed = IsSignedBySecureBootAuthority(buf, len); + if (sb_signed) + img_report.has_secureboot_bootloader |= 1; + uprintf(" • %s%s", img_report.efi_boot_entry[i].path, sb_signed ? "*" : ""); + r = IsBootloaderRevoked(buf, len); + if (r > 0) { + assert(r <= ARRAYSIZE(revocation_type)); + assert(r <= 7); + uprintf(" WARNING: '%s' has been revoked by %s", img_report.efi_boot_entry[i].path, revocation_type[r - 1]); + img_report.has_secureboot_bootloader |= 1 << r; + } + safe_free(buf); + } +} + // The scanning process can be blocking for message processing => use a thread DWORD WINAPI ImageScanThread(LPVOID param) { @@ -1348,6 +1385,7 @@ DWORD WINAPI ImageScanThread(LPVOID param) } if (img_report.is_iso) { + GetBootladerInfo(); DisplayISOProps(); for (i = 0; i < ARRAYSIZE(redhat8_derivative); i++) { @@ -1431,15 +1469,12 @@ out: ExitThread(0); } -#define MAP_BIT(bit) do { map[_log2(bit)] = b; b <<= 1; } while(0) - // Likewise, boot check will block message processing => use a thread static DWORD WINAPI BootCheckThread(LPVOID param) { - int i, r, rr, username_index = -1; + int i, r, username_index = -1; FILE *fd; uint32_t len; - uint8_t* buf = NULL; WPARAM ret = BOOTCHECK_CANCEL; BOOL in_files_dir = FALSE, esp_already_asked = FALSE; BOOL is_windows_to_go = ((image_options & IMOP_WINTOGO) && (ComboBox_GetCurItemData(hImageOption) == IMOP_WIN_TO_GO)); @@ -1638,64 +1673,20 @@ static DWORD WINAPI BootCheckThread(LPVOID param) } } - // Check UEFI bootloaders for revocation - if (IS_EFI_BOOTABLE(img_report)) { - BOOL has_secureboot_signed_bootloader = FALSE; - assert(ARRAYSIZE(img_report.efi_boot_entry) > 0); - PrintStatus(0, MSG_351); - uuprintf("UEFI Secure Boot revocation checks:"); - // Make sure we have at least one regular EFI bootloader that is formally signed - // for Secure Boot, since it doesn't make sense to report revocation otherwise. - for (i = 0; !has_secureboot_signed_bootloader && i < ARRAYSIZE(img_report.efi_boot_entry) && - img_report.efi_boot_entry[i].path[0] != 0; i++) { - if (img_report.efi_boot_entry[i].type == EBT_MAIN) { - len = ReadISOFileToBuffer(image_path, img_report.efi_boot_entry[i].path, &buf); - if (len == 0) { - uprintf("Warning: Failed to extract '%s' to check for UEFI revocation", img_report.efi_boot_entry[i].path); - continue; - } - if (IsSignedBySecureBootAuthority(buf, len)) - has_secureboot_signed_bootloader = TRUE; - free(buf); - } - } - if (!has_secureboot_signed_bootloader) { - uuprintf(" No Secure Boot signed bootloader found -- skipping"); - } else { - rr = 0; - for (i = 0; i < ARRAYSIZE(img_report.efi_boot_entry) && img_report.efi_boot_entry[i].path[0] != 0; i++) { - static const char* revocation_type[] = { "UEFI DBX", "Windows SSP", "Linux SBAT", "Windows SVN", "Cert DBX" }; - len = ReadISOFileToBuffer(image_path, img_report.efi_boot_entry[i].path, &buf); - if (len == 0) { - uprintf("Warning: Failed to extract '%s' to check for UEFI revocation", img_report.efi_boot_entry[i].path); - continue; - } - uuprintf("• %s", img_report.efi_boot_entry[i].path); - r = IsBootloaderRevoked(buf, len); - safe_free(buf); - if (r > 0) { - assert(r <= ARRAYSIZE(revocation_type)); - if (rr == 0) - rr = r; - uprintf("Warning: '%s' has been revoked by %s", img_report.efi_boot_entry[i].path, revocation_type[r - 1]); - is_bootloader_revoked = TRUE; - } - } - if (rr > 0) { - switch (rr) { - case 2: - msg = lmprintf(MSG_341, "Error code: 0xc0000428"); - break; - default: - msg = lmprintf(MSG_340); - break; - } - r = MessageBoxExU(hMainDialog, lmprintf(MSG_339, msg), lmprintf(MSG_338), - MB_OKCANCEL | MB_ICONWARNING | MB_IS_RTL, selected_langid); - if (r == IDCANCEL) - goto out; - } + // Display an alert if any of the UEFI bootloaders have been revoked + if (img_report.has_secureboot_bootloader & 0xfe) { + switch (img_report.has_secureboot_bootloader & 0xfe) { + case 4: + msg = lmprintf(MSG_341, "Error code: 0xc0000428"); + break; + default: + msg = lmprintf(MSG_340); + break; } + r = MessageBoxExU(hMainDialog, lmprintf(MSG_339, msg), lmprintf(MSG_338), + MB_OKCANCEL | MB_ICONWARNING | MB_IS_RTL, selected_langid); + if (r == IDCANCEL) + goto out; } if ((img_report.projected_size < MAX_ISO_TO_ESP_SIZE) && HAS_REGULAR_EFI(img_report) && @@ -4211,6 +4202,10 @@ out: safe_free(pe256ssp); safe_free(sbat_entries); safe_free(sbat_level_txt); + safe_free(sb_active_certs); + safe_free(sb_active_txt); + safe_free(sb_revoked_certs); + safe_free(sb_revoked_txt); if (argv != NULL) { for (i = 0; i < argc; i++) safe_free(argv[i]); diff --git a/src/rufus.h b/src/rufus.h index 9ed9432f..1a703f7f 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -436,6 +436,7 @@ typedef struct { BOOLEAN rh8_derivative; uint16_t winpe; uint16_t has_efi; + uint8_t has_secureboot_bootloader; uint8_t has_md5sum; uint8_t wininst_index; uint8_t has_symlinks; @@ -519,12 +520,6 @@ typedef struct { uint32_t* address; // 32-bit will do, as we're not dealing with >4GB DLLs... } dll_resolver_t; -/* SBAT entry */ -typedef struct { - char* product; - uint32_t version; -} sbat_entry_t; - /* Alignment macro */ #if defined(__GNUC__) #define ALIGNED(m) __attribute__ ((__aligned__(m))) @@ -576,6 +571,18 @@ extern hash_init_t* hash_init[HASH_MAX]; extern hash_write_t* hash_write[HASH_MAX]; extern hash_final_t* hash_final[HASH_MAX]; +/* SBAT entry */ +typedef struct { + char* product; + uint32_t version; +} sbat_entry_t; + +/* Certificate thumbprint list */ +typedef struct { + uint32_t count; + uint8_t list[0][SHA1_HASHSIZE]; +} thumbprint_list_t; + #ifndef __VA_GROUP__ #define __VA_GROUP__(...) __VA_ARGS__ #endif @@ -741,6 +748,7 @@ extern const int nb_steps[FS_MAX]; extern float fScale; extern windows_version_t WindowsVersion; extern sbat_entry_t* sbat_entries; +extern thumbprint_list_t *sb_active_certs, *sb_revoked_certs; extern int dialog_showing, force_update, fs_type, boot_type, partition_type, target_type; extern unsigned long syslinux_ldlinux_len[2]; extern char ubuffer[UBUFFER_SIZE], embedded_sl_version_str[2][12]; @@ -886,6 +894,7 @@ extern HANDLE CreatePreallocatedFile(const char* lpFileName, DWORD dwDesiredAcce DWORD dwFlagsAndAttributes, LONGLONG fileSize); extern uint32_t ResolveDllAddress(dll_resolver_t* resolver); extern sbat_entry_t* GetSbatEntries(char* sbatlevel); +extern thumbprint_list_t* GetThumbprintEntries(char* thumbprints_txt); extern uint16_t GetPeArch(uint8_t* buf); extern uint8_t* GetPeSection(uint8_t* buf, const char* name, uint32_t* len); extern uint8_t* GetPeSignatureData(uint8_t* buf); diff --git a/src/rufus.rc b/src/rufus.rc index 9d1c5ea1..0352fb91 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.8.2249" +CAPTION "Rufus 4.8.2250" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -407,8 +407,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,8,2249,0 - PRODUCTVERSION 4,8,2249,0 + FILEVERSION 4,8,2250,0 + PRODUCTVERSION 4,8,2250,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -426,13 +426,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.8.2249" + VALUE "FileVersion", "4.8.2250" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2025 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.8.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.8.2249" + VALUE "ProductVersion", "4.8.2250" END END BLOCK "VarFileInfo" diff --git a/src/stdlg.c b/src/stdlg.c index 0b0c422b..ad2605a5 100644 --- a/src/stdlg.c +++ b/src/stdlg.c @@ -46,7 +46,7 @@ /* Globals */ extern BOOL is_x86_64, appstore_version; -extern char unattend_username[MAX_USERNAME_LENGTH], *sbat_level_txt; +extern char unattend_username[MAX_USERNAME_LENGTH], *sbat_level_txt, *sb_active_txt, *sb_revoked_txt; extern HICON hSmallIcon, hBigIcon; static HICON hMessageIcon = (HICON)INVALID_HANDLE_VALUE; static char* szMessageText = NULL; @@ -1404,18 +1404,38 @@ static DWORD WINAPI CheckForFidoThread(LPVOID param) safe_free(fido_url); safe_free(sbat_entries); safe_free(sbat_level_txt); + safe_free(sb_active_txt); + safe_free(sb_revoked_txt); // Get the latest sbat_level.txt data while we're poking the network for Fido. len = DownloadToFileOrBuffer(RUFUS_URL "/sbat_level.txt", NULL, (BYTE**)&sbat_level_txt, NULL, FALSE); - if (len != 0 && len < 512) { + if (len != 0 && len < 1 * KB) { sbat_entries = GetSbatEntries(sbat_level_txt); - if (sbat_entries != 0) { + if (sbat_entries != NULL) { for (i = 0; sbat_entries[i].product != NULL; i++); if (i > 0) uprintf("Found %d additional UEFI revocation filters from remote SBAT", i); } } + // Get the active Secure Boot certificate thumbprints + len = DownloadToFileOrBuffer(RUFUS_URL "/sb_active.txt", NULL, (BYTE**)&sb_active_txt, NULL, FALSE); + if (len != 0 && len < 1 * KB) { + sb_active_certs = GetThumbprintEntries(sb_active_txt); + if (sb_active_certs != NULL) { + uprintf("Found %d active Secure Boot certificate entries from remote", sb_active_certs->count); + } + } + + // Get the revoked Secure Boot certificate thumbprints + len = DownloadToFileOrBuffer(RUFUS_URL "/sb_revoked.txt", NULL, (BYTE**)&sb_revoked_txt, NULL, FALSE); + if (len != 0 && len < 1 * KB) { + sb_revoked_certs = GetThumbprintEntries(sb_revoked_txt); + if (sb_revoked_certs != NULL) { + uprintf("Found %d revoked Secure Boot certificate entries from remote", sb_revoked_certs->count); + } + } + // Get the Fido URL from parsing a 'Fido.ver' on our server. This enables the use of different // Fido versions from different versions of Rufus, if needed, as opposed to always downloading // the latest release from GitHub, which may contain incompatible changes...