Skip to content

Commit

Permalink
[iso] add SBAT revocation validation and reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
pbatard committed Oct 3, 2024
1 parent f453dc2 commit c5d61f6
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 104 deletions.
123 changes: 76 additions & 47 deletions src/hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ uint64_t md5sum_totalbytes;
StrArray modified_files = { 0 };

extern int default_thread_priority;
extern const char* efi_bootname[ARCH_MAX];
extern const char* efi_archname[ARCH_MAX];

/*
* Rotate 32 or 64 bit integers by n bytes.
Expand Down Expand Up @@ -1760,36 +1760,18 @@ BOOL efi_image_parse(uint8_t* efi, size_t len, struct efi_image_regions** regp)
* for some part of this but you'd be very, very wrong since the PE sections it
* feeds to the hash function does include the PE header checksum field...
*/
BOOL PE256File(const char* path, uint8_t* hash)
BOOL PE256Buffer(uint8_t* buf, uint32_t len, uint8_t* hash)
{
BOOL r = FALSE;
HASH_CONTEXT hash_ctx = { {0} };
int i;
uint32_t rb;
uint8_t* buf = NULL;
struct __stat64 stat64 = { 0 };
struct efi_image_regions* regs = NULL;

if ((path == NULL) || (hash == NULL))
goto out;

/* Filter anything that would be out of place as a EFI bootloader */
if (_stat64U(path, &stat64) != 0) {
uprintf("Could not open '%s", path);
goto out;
}
if ((stat64.st_size < 1 * KB) || (stat64.st_size > 64 * MB)) {
uprintf("'%s' is either too small or too large for PE-256", path);
goto out;
}

/* Read the executable into a memory buffer */
rb = read_file(path, &buf);
if (rb < 1 * KB)
if ((buf == NULL) || (len == 0) || (len < 1 * KB) || (len > 64 * MB) || (hash == NULL))
goto out;

/* Isolate the PE sections to hash */
if (!efi_image_parse(buf, rb, &regs))
if (!efi_image_parse(buf, len, &regs))
goto out;

/* Hash the relevant PE data */
Expand All @@ -1803,7 +1785,6 @@ BOOL PE256File(const char* path, uint8_t* hash)

out:
free(regs);
free(buf);
return r;
}

Expand Down Expand Up @@ -2098,36 +2079,82 @@ BOOL IsFileInDB(const char* path)
return FALSE;
}

int IsBootloaderRevoked(const char* path)
BOOL IsRevokedBySbat(uint8_t* buf, uint32_t len)
{
static const char section_name[IMAGE_SIZEOF_SHORT_NAME] = { '.', 's', 'b', 'a', 't', '\0', '\0', '\0' };
char* sbat = NULL, * version_str;
uint32_t i, j;
sbat_entry_t entry;
IMAGE_SECTION_HEADER* section_header;

if (buf == NULL || len < 0x100 || sbat_entries == NULL)
return FALSE;

for (i = 0x40; i < MIN(len, 0x800); i++) {
if (memcmp(section_name, &buf[i], sizeof(section_name)) == 0) {
section_header = (IMAGE_SECTION_HEADER*)&buf[i];
if (section_header->SizeOfRawData >= len || section_header->PointerToRawData >= len)
return TRUE;
sbat = (char*)&buf[section_header->PointerToRawData];
break;
}
}
if (sbat == NULL)
return FALSE;

for (i = 0; sbat[i] != '\0'; ) {
while (sbat[i] == '\n')
i++;
if (sbat[i] == '\0')
break;
entry.product = &sbat[i];
for (; sbat[i] != ',' && sbat[i] != '\0' && sbat[i] != '\n'; i++);
if (sbat[i] == '\0' || sbat[i] == '\n')
break;
sbat[i++] = '\0';
version_str = &sbat[i];
for (; sbat[i] != ',' && sbat[i] != '\0' && sbat[i] != '\n'; i++);
sbat[i++] = '\0';
entry.version = atoi(version_str);
for (; sbat[i] != '\0' && sbat[i] != '\n'; i++);
if (entry.version == 0)
continue;
for (j = 0; sbat_entries[j].product != NULL; j++) {
if (strcmp(entry.product, sbat_entries[j].product) == 0 && entry.version < sbat_entries[j].version)
return TRUE;
}
}

return FALSE;
}

int IsBootloaderRevoked(uint8_t* buf, uint32_t len)
{
version_t* ver;
uint32_t i;
uint8_t hash[SHA256_HASHSIZE];

if (!PE256File(path, hash))
if (!PE256Buffer(buf, len, hash))
return -1;
// Check for UEFI DBX revocation
for (i = 0; i < ARRAYSIZE(pe256dbx); i += SHA256_HASHSIZE)
if (memcmp(hash, &pe256dbx[i], SHA256_HASHSIZE) == 0)
return 1;
// Check for Microsoft SSP revocation
for (i = 0; i < pe256ssp_size * SHA256_HASHSIZE; i += SHA256_HASHSIZE)
if (memcmp(hash, &pe256ssp[i], SHA256_HASHSIZE) == 0)
return 2;
ver = GetExecutableVersion(path);
// Blanket filter for Windows 10 1607 (excluded) to Windows 10 20H1 (excluded)
// TODO: Revoke all bootloaders prior to 2023.05 once Microsoft does
// uprintf("Found UEFI bootloader version: %d.%d.%d.%d", ver->Major, ver->Minor, ver->Micro, ver->Nano);
if (ver != NULL && ver->Major == 10 && ver->Minor == 0 && ver->Micro > 14393 && ver->Micro < 19041)
// Check for SBAT revocation
if (IsRevokedBySbat(buf, len))
return 3;

return 0;
}

void PrintRevokedBootloaderInfo(void)
{
uprintf("Found %d officially revoked UEFI bootloaders from embedded list", sizeof(pe256dbx) / SHA256_HASHSIZE);
if (ParseSKUSiPolicy())
uprintf("Found %d revoked UEFI bootloaders from embedded list", sizeof(pe256dbx) / SHA256_HASHSIZE);
if (ParseSKUSiPolicy() && pe256ssp_size != 0)
uprintf("Found %d additional revoked UEFI bootloaders from this system's SKUSiPolicy.p7b", pe256ssp_size);
else
uprintf("WARNING: Could not parse this system's SkuSiPolicy.p7b for additional revoked UEFI bootloaders");
}

/*
Expand All @@ -2148,8 +2175,8 @@ void UpdateMD5Sum(const char* dest_dir, const char* md5sum_name)
intptr_t pos;
uint32_t i, j, size, md5_size, new_size;
uint8_t sum[MD5_HASHSIZE];
char md5_path[64], path1[64], path2[64], *md5_data = NULL, *new_data = NULL, *str_pos;
char *d, *s, *p;
char md5_path[64], path1[64], path2[64], bootloader_name[32];
char *md5_data = NULL, *new_data = NULL, *str_pos, *d, *s, *p;

if (!img_report.has_md5sum && !validate_md5sum)
goto out;
Expand Down Expand Up @@ -2200,12 +2227,13 @@ void UpdateMD5Sum(const char* dest_dir, const char* md5sum_name)
}
s = md5_data;
// Extract the MD5Sum bootloader(s)
for (i = 1; i < ARRAYSIZE(efi_bootname); i++) {
static_sprintf(path1, "%s\\efi\\boot\\%s", dest_dir, efi_bootname[i]);
for (i = 1; i < ARRAYSIZE(efi_archname); i++) {
static_sprintf(bootloader_name, "boot%s.efi", efi_archname[i]);
static_sprintf(path1, "%s\\efi\\boot\\boot%s.efi", dest_dir, efi_archname[i]);
if (!PathFileExistsA(path1))
continue;
res_data = (BYTE*)GetResource(hMainInstance, MAKEINTRESOURCEA(IDR_MD5_BOOT + i),
_RT_RCDATA, efi_bootname[i], &res_size, FALSE);
_RT_RCDATA, bootloader_name, &res_size, FALSE);
static_strcpy(path2, path1);
path2[strlen(path2) - 4] = 0;
static_strcat(path2, "_original.efi");
Expand All @@ -2232,20 +2260,21 @@ void UpdateMD5Sum(const char* dest_dir, const char* md5sum_name)
}
// Rename the original bootloaders if present in md5sum.txt
for (p = md5_data; (p = StrStrIA(p, " ./efi/boot/boot")) != NULL; ) {
for (i = 1; i < ARRAYSIZE(efi_bootname); i++) {
if (p[12 + strlen(efi_bootname[i])] != 0x0a)
for (i = 1; i < ARRAYSIZE(efi_archname); i++) {
static_sprintf(bootloader_name, "boot%s.efi", efi_archname[i]);
if (p[12 + strlen(bootloader_name)] != 0x0a)
continue;
p[12 + strlen(efi_bootname[i])] = 0;
if (lstrcmpiA(&p[12], efi_bootname[i]) == 0) {
size = (uint32_t)(p - s) + 12 + (uint32_t)strlen(efi_bootname[i]) - 4;
p[12 + strlen(bootloader_name)] = 0;
if (lstrcmpiA(&p[12], bootloader_name) == 0) {
size = (uint32_t)(p - s) + 12 + (uint32_t)strlen(bootloader_name) - 4;
memcpy(d, s, size);
d = &d[size];
strcpy(d, "_original.efi\n");
new_size += 9;
d = &d[14];
s = &p[12 + strlen(efi_bootname[i]) + 1];
s = &p[12 + strlen(bootloader_name) + 1];
}
p[12 + strlen(efi_bootname[i])] = 0x0a;
p[12 + strlen(bootloader_name)] = 0x0a;
}
p = &p[12];
}
Expand Down
47 changes: 26 additions & 21 deletions src/iso.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,8 @@ const char* md5sum_name[2] = { "md5sum.txt", "MD5SUMS" };
static const char* casper_dirname = "/casper";
static const char* proxmox_dirname = "/proxmox";
const char* efi_dirname = "/efi/boot";
const char* efi_bootname[ARCH_MAX] = {
"boot.efi", "bootia32.efi", "bootx64.efi", "bootarm.efi", "bootaa64.efi", "bootia64.efi",
"bootriscv32.efi", "bootriscv64.efi", "bootriscv128.efi", "bootebc.efi" };
const char* efi_bootname[3] = { "boot", "grub", "mm" };
const char* efi_archname[ARCH_MAX] = { "", "ia32", "x64", "arm", "aa64", "ia64", "riscv64", "ebc" };
static const char* sources_str = "/sources";
static const char* wininst_name[] = { "install.wim", "install.esd", "install.swm" };
// We only support GRUB/BIOS (x86) that uses a standard config dir (/boot/grub/i386-pc/)
Expand Down Expand Up @@ -146,8 +145,8 @@ static __inline char* sanitize_filename(char* filename, BOOL* is_identical)
}

// Must start after the drive part (D:\...) so that we don't eliminate the first column
for (i=2; i<safe_strlen(ret); i++) {
for (j=0; j<sizeof(unauthorized); j++) {
for (i = 2; i<safe_strlen(ret); i++) {
for (j = 0; j<sizeof(unauthorized); j++) {
if (ret[i] == unauthorized[j]) {
ret[i] = '_';
*is_identical = FALSE;
Expand All @@ -169,7 +168,8 @@ static void log_handler (cdio_log_level_t level, const char *message)
static BOOL check_iso_props(const char* psz_dirname, int64_t file_length, const char* psz_basename,
const char* psz_fullpath, EXTRACT_PROPS *props)
{
size_t i, j, len;
size_t i, j, k, len;
char bootloader_name[32];

// Check for an isolinux/syslinux config file anywhere
memset(props, 0, sizeof(EXTRACT_PROPS));
Expand Down Expand Up @@ -285,13 +285,17 @@ static BOOL check_iso_props(const char* psz_dirname, int64_t file_length, const

// Check for the EFI boot entries
if (safe_stricmp(psz_dirname, efi_dirname) == 0) {
for (i = 0; i < ARRAYSIZE(efi_bootname); i++) {
if (safe_stricmp(psz_basename, efi_bootname[i]) == 0) {
img_report.has_efi |= (2 << i); // start at 2 since "bootmgr.efi" is bit 0
for (j = 0; j < ARRAYSIZE(img_report.efi_boot_path); j++) {
if (img_report.efi_boot_path[j][0] == 0) {
static_strcpy(img_report.efi_boot_path[j], psz_fullpath);
break;
for (k = 0; k < ARRAYSIZE(efi_bootname); k++) {
for (i = 0; i < ARRAYSIZE(efi_archname); i++) {
static_sprintf(bootloader_name, "%s%s.efi", efi_bootname[k], efi_archname[i]);
if (safe_stricmp(psz_basename, bootloader_name) == 0) {
if (k == 0)
img_report.has_efi |= (2 << i); // start at 2 since "bootmgr.efi" is bit 0
for (j = 0; j < ARRAYSIZE(img_report.efi_boot_path); j++) {
if (img_report.efi_boot_path[j][0] == 0) {
static_strcpy(img_report.efi_boot_path[j], psz_fullpath);
break;
}
}
}
}
Expand All @@ -302,7 +306,7 @@ static BOOL check_iso_props(const char* psz_dirname, int64_t file_length, const
// https://salsa.debian.org/live-team/live-build/-/commit/5bff71fea2dd54adcd6c428d3f1981734079a2f7
// Because of this, if we detect a small bootx64.efi file, we assert that it's a
// broken link and try to extract a "good" version from the El-Torito image.
if ((safe_stricmp(psz_basename, efi_bootname[2]) == 0) && (file_length < 256)) {
if ((safe_stricmp(psz_basename, "bootx64.efi") == 0) && (file_length < 256)) {
img_report.has_efi |= 0x4000;
static_strcpy(img_report.efi_img_path, "[BOOT]/1-Boot-NoEmul.img");
}
Expand Down Expand Up @@ -1747,7 +1751,7 @@ BOOL HasEfiImgBootLoaders(void)
int32_t dc, c;
struct libfat_filesystem *lf_fs = NULL;
struct libfat_direntry direntry;
char name[12] = { 0 };
char name[12] = { 0 }, bootloader_name[32];
int i, j, k;

if ((image_path == NULL) || !HAS_EFI_IMG(img_report))
Expand Down Expand Up @@ -1788,23 +1792,24 @@ BOOL HasEfiImgBootLoaders(void)
goto out;
dc = direntry.entry[26] + (direntry.entry[27] << 8);

for (i = 0; i < ARRAYSIZE(efi_bootname); i++) {
for (i = 0; i < ARRAYSIZE(efi_archname); i++) {
static_sprintf(bootloader_name, "boot%s.efi", efi_archname[i]);
// TODO: bootriscv###.efi will need LFN support but cross that bridge when/if we get there...
if (strlen(efi_bootname[i]) > 12)
if (strlen(bootloader_name) > 12)
continue;
for (j = 0, k = 0; efi_bootname[i][j] != 0; j++) {
if (efi_bootname[i][j] == '.') {
for (j = 0, k = 0; bootloader_name[j] != 0; j++) {
if (bootloader_name[j] == '.') {
while (k < 8)
name[k++] = ' ';
} else {
name[k++] = toupper(efi_bootname[i][j]);
name[k++] = toupper(bootloader_name[j]);
}
}
c = libfat_searchdir(lf_fs, dc, name, &direntry);
if (c > 0) {
if (!ret)
uprintf(" Detected EFI bootloader(s) (from '%s'):", img_report.efi_img_path);
uprintf(" ● '%s'", efi_bootname[i]);
uprintf(" ● '%s'", bootloader_name);
ret = TRUE;
}
}
Expand Down
51 changes: 51 additions & 0 deletions src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1544,3 +1544,54 @@ int sanitize_label(char* label)

return 0;
}

/*
* Parse an sbat_level.txt file and returns an array of (product_name, min_version) tuples.
* Array must be freed by caller.
*/
sbat_entry_t* GetSbatEntries(char* sbatlevel)
{
BOOL eol, eof;
char* version_str;
uint32_t i, num_entries;
sbat_entry_t* sbat_entries;

if (sbatlevel == NULL)
return FALSE;

num_entries = 0;
for (i = 0; sbatlevel[i] != '\0'; i++)
if (sbatlevel[i] == '\n')
num_entries++;

sbat_entries = calloc(num_entries + 2, sizeof(sbat_entry_t));
if (sbat_entries == NULL)
return NULL;

num_entries = 0;
for (i = 0; sbatlevel[i] != '\0'; ) {
while (sbatlevel[i] == '\n')
i++;
if (sbatlevel[i] == '\0')
break;
sbat_entries[num_entries].product = &sbatlevel[i];
for (; sbatlevel[i] != ',' && sbatlevel[i] != '\0' && sbatlevel[i] != '\n'; i++);
if (sbatlevel[i] == '\0' || sbatlevel[i] == '\n')
break;
sbatlevel[i++] = '\0';
version_str = &sbatlevel[i];
for (; sbatlevel[i] != ',' && sbatlevel[i] != '\0' && sbatlevel[i] != '\n'; i++);
eol = (sbatlevel[i] == '\0' || sbatlevel[i] == '\n');
eof = (sbatlevel[i] == '\0');
sbatlevel[i] = '\0';
if (!eof)
i++;
sbat_entries[num_entries].version = atoi(version_str);
if (!eol)
for (; sbatlevel[i] != '\0' && sbatlevel[i] != '\n'; i++);
if (sbat_entries[num_entries].version != 0)
num_entries++;
}

return sbat_entries;
}
Loading

0 comments on commit c5d61f6

Please sign in to comment.