Skip to content

Commit

Permalink
[md5sum] Add md5sum.txt creation and runtime UEFI validation
Browse files Browse the repository at this point in the history
* This allows *runtime* validation of UEFI bootable media, such as Windows
  or Linux installers, which, considering the unreliability of USB flash
  drives, we assert is a a much better proposal than write-time validation
  that utilities like balenaEcther (and to a lesser extent MCT) provide.
* Based on uefi-md5sum (https://github.com/pbatard/uefi-md5sum).
* Unconditionally activated on ISO extraction for GPT targets for now.
  This will be changed to a user selectable option later.
  • Loading branch information
pbatard committed Mar 29, 2024
1 parent b7568ab commit 52ca798
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 126 deletions.
10 changes: 7 additions & 3 deletions src/format.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ static float format_percent = 0.0f;
static int task_number = 0, actual_fs_type;
static unsigned int sec_buf_pos = 0;
extern const int nb_steps[FS_MAX];
extern const char* md5sum_name[2];
extern uint32_t dur_mins, dur_secs;
extern uint32_t wim_nb_files, wim_proc_files, wim_extra_files;
extern BOOL force_large_fat32, enable_ntfs_compression, lock_drive, zero_drive, fast_zeroing, enable_file_indexing;
Expand Down Expand Up @@ -1035,11 +1036,11 @@ BOOL WritePBR(HANDLE hLogicalVolume)
uprintf(using_msg, bt_to_name(), "FAT32");
for (i = 0; i < 2; i++) {
if (!is_fat_32_fs(fp)) {
uprintf("New volume does not have a %s FAT32 boot sector - aborting", i?"secondary":"primary");
uprintf("New volume does not have a %s FAT32 boot sector - aborting", i ? "secondary" : "primary");
break;
}
uprintf("Confirmed new volume has a %s FAT32 boot sector", i?"secondary":"primary");
uprintf("Setting %s FAT32 boot sector for boot...", i?"secondary":"primary");
uprintf("Confirmed new volume has a %s FAT32 boot sector", i ? "secondary" : "primary");
uprintf("Setting %s FAT32 boot sector for boot...", i ? "secondary" : "primary");
if (boot_type == BT_FREEDOS) {
if (!write_fat_32_fd_br(fp, 0)) break;
} else if (boot_type == BT_REACTOS) {
Expand Down Expand Up @@ -1964,8 +1965,11 @@ DWORD WINAPI FormatThread(void* param)
}
}
}

UpdateProgress(OP_FINALIZE, -1.0f);
PrintInfoDebug(0, MSG_233);
if ((boot_type == BT_IMAGE) && (image_path != NULL) && (img_report.is_iso) && (!windows_to_go))
UpdateMD5Sum(drive_name, md5sum_name[img_report.has_md5sum ? img_report.has_md5sum - 1 : 0]);
if (IsChecked(IDC_EXTENDED_LABEL))
SetAutorun(drive_name);
// Issue another complete remount before we exit, to ensure we're clean
Expand Down
144 changes: 140 additions & 4 deletions src/hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#endif

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
Expand Down Expand Up @@ -103,14 +104,18 @@

/* Globals */
char hash_str[HASH_MAX][150];
uint32_t proc_bufnum, hash_count[HASH_MAX] = { MD5_HASHSIZE, SHA1_HASHSIZE, SHA256_HASHSIZE, SHA512_HASHSIZE };
HANDLE data_ready[HASH_MAX] = { 0 }, thread_ready[HASH_MAX] = { 0 };
DWORD read_size[NUM_BUFFERS];
BOOL enable_extra_hashes = FALSE;
BOOL enable_extra_hashes = FALSE, validate_md5sum = FALSE;
uint8_t ALIGNED(64) buffer[NUM_BUFFERS][BUFFER_SIZE];
extern int default_thread_priority;
uint32_t pe256ssp_size = 0;
uint8_t* pe256ssp = NULL;
uint32_t proc_bufnum, hash_count[HASH_MAX] = { MD5_HASHSIZE, SHA1_HASHSIZE, SHA256_HASHSIZE, SHA512_HASHSIZE };
uint32_t pe256ssp_size = 0;
uint64_t md5sum_totalbytes;
StrArray modified_files = { 0 };

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

/*
* Rotate 32 or 64 bit integers by n bytes.
Expand Down Expand Up @@ -2122,6 +2127,137 @@ void PrintRevokedBootloaderInfo(void)
uprintf("WARNING: Could not parse this system's SkuSiPolicy.p7b for additional revoked UEFI bootloaders");
}

/*
* Updates the MD5SUMS/md5sum.txt file that some distros (Ubuntu, Mint...)
* use to validate the media. Because we may alter some of the validated files
* to add persistence and whatnot, we need to alter the MD5 list as a result.
* The format of the file is expected to always be "<MD5SUM> <FILE_PATH>" on
* individual lines.
* This function is also used to finalize the md5sum.txt we create for use with
* our uefi-md5sum bootloaders.
*/
void UpdateMD5Sum(const char* dest_dir, const char* md5sum_name)
{
BOOL display_header = TRUE;
BYTE* res_data;
DWORD res_size;
HANDLE hFile;
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;

if (!img_report.has_md5sum && !validate_md5sum)
goto out;

static_sprintf(md5_path, "%s\\%s", dest_dir, md5sum_name);
md5_size = read_file(md5_path, (uint8_t**)&md5_data);
if (md5_size == 0)
goto out;

for (i = 0; i < modified_files.Index; i++) {
for (j = 0; j < (uint32_t)strlen(modified_files.String[i]); j++)
if (modified_files.String[i][j] == '\\')
modified_files.String[i][j] = '/';
str_pos = strstr(md5_data, &modified_files.String[i][2]);
if (str_pos == NULL)
// File is not listed in md5 sums
continue;
if (display_header) {
uprintf("Updating %s:", md5_path);
display_header = FALSE;
}
uprintf("● %s", &modified_files.String[i][2]);
pos = str_pos - md5_data;
HashFile(HASH_MD5, modified_files.String[i], sum);
while ((pos > 0) && (md5_data[pos - 1] != '\n'))
pos--;
assert(IS_HEXASCII(md5_data[pos]));
for (j = 0; j < 16; j++) {
md5_data[pos + 2 * j] = ((sum[j] >> 4) < 10) ? ('0' + (sum[j] >> 4)) : ('a' - 0xa + (sum[j] >> 4));
md5_data[pos + 2 * j + 1] = ((sum[j] & 15) < 10) ? ('0' + (sum[j] & 15)) : ('a' - 0xa + (sum[j] & 15));
}
}

// If we validate md5sum we need to update the original bootloader names and add md5sum_totalbytes
if (validate_md5sum) {
new_size = md5_size;
new_data = malloc(md5_size + 1024);
assert(new_data != NULL);
if (new_data == NULL)
goto out;
// Will be nonzero if we created the file, otherwise zero
if (md5sum_totalbytes != 0) {
snprintf(new_data, md5_size + 1024, "# md5sum_totalbytes = 0x%llx\n", md5sum_totalbytes);
new_size += (uint32_t)strlen(new_data);
d = &new_data[strlen(new_data)];
} else {
d = new_data;
}
s = md5_data;
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)
continue;
p[12 + strlen(efi_bootname[i])] = 0;
if (lstrcmpiA(&p[12], efi_bootname[i]) == 0) {
res_data = (BYTE*)GetResource(hMainInstance, MAKEINTRESOURCEA(IDR_MD5_BOOT + i),
_RT_RCDATA, efi_bootname[i], &res_size, FALSE);
static_sprintf(path1, "%s\\%s", dest_dir, &p[3]);
for (j = 0; j < strlen(path1); j++)
if (path1[j] == '/') path1[j] = '\\';
static_strcpy(path2, path1);
path2[strlen(path2) - 4] = 0;
static_strcat(path2, "_original.efi");
if (res_data != NULL && MoveFileU(path1, path2)) {
uprintf("Renamed: %s → %s", path1, path2);
hFile = CreateFileA(path1, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if ((hFile == NULL) || (hFile == INVALID_HANDLE_VALUE)) {
uprintf("Could not create '%s': %s.", path1, WindowsErrorString());
MoveFileU(path2, path1);
continue;
}
if (!WriteFileWithRetry(hFile, res_data, res_size, NULL, WRITE_RETRIES)) {
uprintf("Could not write '%s': %s.", path1, WindowsErrorString());
safe_closehandle(hFile);
MoveFileU(path2, path1);
continue;
}
safe_closehandle(hFile);
uprintf("Created: %s (%s)", path1, SizeToHumanReadable(res_size, FALSE, FALSE));
size = (uint32_t)(p - s) + 12 + (uint32_t)strlen(efi_bootname[i]) - 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];
// TODO: Update sources/boot.wim if we modified it
// Also, we'll need to keep a copy of the size of boot.wim BEFORE we alter it
// so we can adjust md5sum_totalbytes
}
}
p[12 + strlen(efi_bootname[i])] = 0x0a;
}
p = &p[12];
}
p = &md5_data[md5_size];
memcpy(d, s, p - s);
free(md5_data);
md5_data = new_data;
md5_size = new_size;
}

write_file(md5_path, md5_data, md5_size);
free(md5_data);

out:
// We no longer need the string array at this stage
StrArrayDestroy(&modified_files);
}

#if defined(_DEBUG) || defined(TEST) || defined(ALPHA)
/* Convert a lowercase hex string to binary. Returned value must be freed */
uint8_t* to_bin(const char* str)
Expand Down
Loading

0 comments on commit 52ca798

Please sign in to comment.