Skip to content

Commit

Permalink
Add chain loading of boot###_original.efi if present
Browse files Browse the repository at this point in the history
* In case this bootloader relies on specific path (e.g. config file lookup),
  expect it in the /efi/boot/ directory with a '_original.efi' suffix.
  • Loading branch information
pbatard committed Feb 19, 2024
1 parent 9dc77f8 commit aae9821
Show file tree
Hide file tree
Showing 11 changed files with 301 additions and 22 deletions.
1 change: 1 addition & 0 deletions .vs/uefi-md5sum.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@
<ClCompile Include="..\boot.c" />
<ClCompile Include="..\hash.c" />
<ClCompile Include="..\parse.c" />
<ClCompile Include="..\path.c" />
<ClCompile Include="..\system.c" />
<ClCompile Include="..\utf8.c" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions .vs/uefi-md5sum.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
<ClCompile Include="..\utf8.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\path.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\boot.h">
Expand Down
2 changes: 1 addition & 1 deletion Md5SumPkg.dec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## @file
# MD5Sum Package
#
# Copyright (c) 2023, Pete Batard <pete@akeo.ie>
# Copyright (c) 2023-2024, Pete Batard <pete@akeo.ie>
#
##

Expand Down
2 changes: 1 addition & 1 deletion Md5SumPkg.dsc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## @file
# MD5Sum Bootloader
#
# Copyright (c) 2023, Pete Batard <pete@akeo.ie>
# Copyright (c) 2023-2024, Pete Batard <pete@akeo.ie>
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
Expand Down
3 changes: 2 additions & 1 deletion Md5SumPkg.inf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# This bootloader performs MD5 sum validation from md5sum.txt,
# and then hands over to the orginial bootloader (boot###.org)
#
# Copyright (c) 2023, Pete Batard <pete@akeo.ie>
# Copyright (c) 2023-2024, Pete Batard <pete@akeo.ie>
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
Expand All @@ -22,6 +22,7 @@
boot.c
hash.c
parse.c
path.c
system.c
utf8.c

Expand Down
2 changes: 1 addition & 1 deletion Md5SumPkg.uni
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// /** @file
// Module implementation for the MD5Sum Bootloader
//
// Copyright (c) 2023, Pete Batard <pete@akeo.ie>
// Copyright (c) 2023-2024, Pete Batard <pete@akeo.ie>
//
// SPDX-License-Identifier: GPL-2.0-or-later
//
Expand Down
144 changes: 126 additions & 18 deletions boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ BOOLEAN IsTestMode = FALSE;
UINTN AlertYPos = ROWS_MIN / 2 + 1;

/* We'll use this string to erase lines on the console */
STATIC CHAR16 EmptyLine[STRING_MAX];
STATIC CHAR16 EmptyLine[STRING_MAX] = { 0 };

/* Keep a copy of the main Image Handle */
STATIC EFI_HANDLE MainImageHandle = NULL;
Expand All @@ -43,10 +43,25 @@ STATIC struct {
UINTN Rows;
} Console = { COLS_MIN, ROWS_MIN };

/* Strings used for platform identification */
#if defined(_M_X64) || defined(__x86_64__)
STATIC CHAR16* Arch = L"x64";
#elif defined(_M_IX86) || defined(__i386__)
STATIC CHAR16* Arch = L"ia32";
#elif defined (_M_ARM64) || defined(__aarch64__)
STATIC CHAR16* Arch = L"aa64";
#elif defined (_M_ARM) || defined(__arm__)
STATIC CHAR16* Arch = L"arm";
#elif defined(_M_RISCV64) || (defined (__riscv) && (__riscv_xlen == 64))
STATIC CHAR16* Arch = L"riscv64";
#else
#error Unsupported architecture
#endif

/* Variables used for progress tracking */
STATIC UINTN ProgressLastCol = 0;
STATIC BOOLEAN ProgressInit = FALSE;
STATIC UINTN ProgressYPos = 5;
STATIC UINTN ProgressYPos = ROWS_MIN / 2;
STATIC UINTN ProgressPPos = 0;

/**
Expand All @@ -62,9 +77,13 @@ STATIC VOID PrintCentered(
{
UINTN MessagePos;

MessagePos = Console.Cols / 2 - SafeStrLen(Message) / 2;
V_ASSERT(MessagePos > MARGIN_H);
SetTextPosition(MessagePos, YPos);
if (!IsTestMode) {
MessagePos = Console.Cols / 2 - SafeStrLen(Message) / 2;
V_ASSERT(MessagePos > MARGIN_H);
SetTextPosition(0, YPos);
Print(EmptyLine);
SetTextPosition(MessagePos, YPos);
}
Print(L"%s\n", Message);
}

Expand Down Expand Up @@ -105,13 +124,89 @@ STATIC VOID PrintFailedEntry(
}

/**
Exit-specific processing
Display a countdown on screen.
@param[in] Message A message to display with the countdown.
@param[in] Duration The duration of the countdown (in ms).
**/
STATIC VOID CountDown(
IN CHAR16* Message,
IN UINTN Duration
)
{
UINTN MessagePos, CounterPos;
INTN i;

if (IsTestMode)
return;

MessagePos = Console.Cols / 2 - SafeStrLen(Message) / 2 - 1;
CounterPos = MessagePos + SafeStrLen(Message) + 2;
V_ASSERT(MessagePos > MARGIN_H);
SetTextPosition(0, Console.Rows - 2);
Print(EmptyLine);
SetTextPosition(MessagePos, Console.Rows - 2);
SetText(TEXT_YELLOW);
Print(L"[%s ", Message);

gST->ConIn->Reset(gST->ConIn, FALSE);
for (i = (INTN)Duration; i >= 0; i -= 200) {
// Allow the user to press a key to interrupt the countdown
if (gST->BootServices->CheckEvent(gST->ConIn->WaitForKey) != EFI_NOT_READY)
break;
if (i % 1000 == 0) {
SetTextPosition(CounterPos, Console.Rows - 2);
Print(L"%d] ", i / 1000);
}
Sleep(200000);
}
}

/**
Process exit according to the multiple scenarios we want to handle
(Chain load the next bootloader, shutdown if test mode, etc.).
@param[in] Status The current EFI Status of the application.
@param[in] DevicePath (Optional) Device Path of a bootloader to chain load.
**/
STATIC VOID ExitCheck(
IN EFI_STATUS Status
STATIC EFI_STATUS ExitProcess(
IN EFI_STATUS Status,
IN EFI_DEVICE_PATH* DevicePath
)
{
UINTN Index;
EFI_HANDLE ImageHandle;
EFI_INPUT_KEY Key;
BOOLEAN RunCountDown = TRUE;

// If we have a bootloader to chain load, try to launch it
if (DevicePath != NULL) {
if (EFI_ERROR(Status) && !IsTestMode) {
// Ask the user if they want to continue
SetText(TEXT_YELLOW);
PrintCentered(L"Proceed with boot? [y/N]", Console.Rows - 2);
gST->ConIn->Reset(gST->ConIn, FALSE);
while (gST->ConIn->ReadKeyStroke(gST->ConIn, &Key) == EFI_NOT_READY);
if (Key.UnicodeChar != L'y' && Key.UnicodeChar != L'Y') {
SafeFree(DevicePath);
return Status;
}
RunCountDown = FALSE;
}
Status = gBS->LoadImage(FALSE, MainImageHandle, DevicePath, NULL, 0, &ImageHandle);
SafeFree(DevicePath);
if (Status == EFI_SUCCESS) {
if (RunCountDown)
CountDown(L"Launching next bootloader in", 3000);
if (!IsTestMode)
gST->ConOut->ClearScreen(gST->ConOut);
Status = gBS->StartImage(ImageHandle, NULL, NULL);
}
if (EFI_ERROR(Status)) {
SetTextPosition(MARGIN_H, Console.Rows / 2 + 1);
PrintError(L"Could not launch original bootloader");
}
}

// If running in test mode, shut down QEMU
if (IsTestMode)
Expand All @@ -122,7 +217,7 @@ STATIC VOID ExitCheck(
if (EFI_ERROR(Status)) {
#endif
SetText(TEXT_YELLOW);
PrintCentered(L" [Press any key to exit] ", Console.Rows - 2);
PrintCentered(L"[Press any key to exit]", Console.Rows - 2);
DefText();
gST->ConIn->Reset(gST->ConIn, FALSE);
gST->BootServices->WaitForEvent(1, &gST->ConIn->WaitForKey, &Index);
Expand All @@ -131,25 +226,28 @@ STATIC VOID ExitCheck(
# else
}
#endif
return Status;
}

/**
Obtain the root handle of the current volume.
Obtain the device and root handle of the current volume.
@param[out] DeviceHandle A pointer to the device handle.
@param[out] Root A pointer to the root file handle.
@retval EFI_SUCCESS The root file handle was successfully populated.
@retval EFI_INVALID_PARAMETER The pointer to the root file handle is invalid.
**/
STATIC EFI_STATUS GetRootHandle(
OUT EFI_HANDLE* DeviceHandle,
OUT EFI_FILE_HANDLE* Root
)
{
EFI_STATUS Status;
EFI_LOADED_IMAGE_PROTOCOL* LoadedImage;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* Volume;

if (Root == NULL)
if (DeviceHandle == NULL || Root == NULL)
return EFI_INVALID_PARAMETER;

// Access the loaded image so we can open the current volume
Expand All @@ -158,6 +256,7 @@ STATIC EFI_STATUS GetRootHandle(
NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (EFI_ERROR(Status))
return Status;
*DeviceHandle = LoadedImage->DeviceHandle;

// Open the the root directory on the boot volume
Status = gBS->OpenProtocol(LoadedImage->DeviceHandle,
Expand Down Expand Up @@ -253,15 +352,17 @@ STATIC VOID PrintProgress(
*/
EFI_STATUS EFIAPI efi_main(
IN EFI_HANDLE BaseImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
IN EFI_SYSTEM_TABLE* SystemTable
)
{
EFI_STATUS Status;
EFI_HANDLE DeviceHandle;
EFI_FILE_HANDLE Root;
EFI_DEVICE_PATH* DevicePath = NULL;
EFI_INPUT_KEY Key;
HASH_LIST HashList = { 0 };
CHAR8 c;
CHAR16 Path[PATH_MAX + 1], Message[128];
CHAR16 Path[PATH_MAX + 1], Message[128], LoaderPath[64];
UINT8 ComputedHash[MD5_HASHSIZE], ExpectedHash[MD5_HASHSIZE];
UINTN i, Index, NumFailed = 0;

Expand All @@ -278,7 +379,8 @@ EFI_STATUS EFIAPI efi_main(
IsTestMode = IsTestSystem();

// Clear the console
gST->ConOut->ClearScreen(gST->ConOut);
if (!IsTestMode)
gST->ConOut->ClearScreen(gST->ConOut);

// Find the amount of console real-estate we have at out disposal
Status = gST->ConOut->QueryMode(gST->ConOut, gST->ConOut->Mode->Mode,
Expand All @@ -302,12 +404,18 @@ EFI_STATUS EFIAPI efi_main(
PrintCentered(L"https://md5.akeo.ie", 0);
DefText();

Status = GetRootHandle(&Root);
Status = GetRootHandle(&DeviceHandle, &Root);
if (EFI_ERROR(Status)) {
PrintError(L"Could not open root directory\n");
goto out;
}

// Look up the original boot loader for chain loading
UnicodeSPrint(LoaderPath, ARRAY_SIZE(LoaderPath),
L"\\efi\\boot\\boot%s_original.efi", Arch);
if (SetPathCase(Root, LoaderPath) == EFI_SUCCESS)
DevicePath = FileDevicePath(DeviceHandle, LoaderPath);

// Parse md5sum.txt to construct a hash list
Status = Parse(Root, HASH_FILE, &HashList);
if (EFI_ERROR(Status))
Expand Down Expand Up @@ -381,7 +489,7 @@ EFI_STATUS EFIAPI efi_main(

out:
SafeFree(HashList.Buffer);
ExitCheck(Status);

return Status;
if (Status == EFI_SUCCESS && NumFailed != 0)
Status = EFI_CRC_ERROR;
return ExitProcess(Status, DevicePath);
}
60 changes: 60 additions & 0 deletions boot.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,53 @@ STATIC __inline UINTN _SafeStrLen(CONST CHAR16 * String, CONST CHAR8 * File, CON

#define SafeStrLen(s) _SafeStrLen(s, __FILE__, __LINE__)

/*
* Some UEFI firmwares have a *BROKEN* Unicode collation implementation
* so we must provide our own version of StriCmp for ASCII comparison...
*/
static __inline CHAR16 _tolower(CONST CHAR16 c)
{
if (('A' <= c) && (c <= 'Z'))
return 'a' + (c - 'A');
return c;
}

STATIC __inline INTN _StriCmp(CONST CHAR16* s1, CONST CHAR16* s2)
{
/* NB: SafeStrLen() will already have asserted if these condition are met */
if ((SafeStrLen(s1) >= STRING_MAX) || (SafeStrLen(s2) >= STRING_MAX))
return -1;
while ((*s1 != L'\0') && (_tolower(*s1) == _tolower(*s2)))
s1++, s2++;
return (INTN)(*s1 - *s2);
}

/*
* Secure string copy, that either uses the already secure version from
* EDK2, or duplicates it for gnu-efi and asserts on any error.
*/
STATIC __inline VOID _SafeStrCpy(CHAR16* Destination, UINTN DestMax,
CONST CHAR16* Source, CONST CHAR8* File, CONST UINTN Line) {
#ifdef _GNU_EFI
P_ASSERT(File, Line, Destination != NULL);
P_ASSERT(File, Line, Source != NULL);
P_ASSERT(File, Line, DestMax != 0);
/*
* EDK2 would use RSIZE_MAX, but we use the smaller PATH_MAX for
* gnu-efi as it can help detect path overflows while debugging.
*/
P_ASSERT(File, Line, DestMax <= PATH_MAX);
P_ASSERT(File, Line, DestMax > StrLen(Source));
while (*Source != 0)
*(Destination++) = *(Source++);
*Destination = 0;
#else
P_ASSERT(File, Line, StrCpyS(Destination, DestMax, Source) == 0);
#endif
}

#define SafeStrCpy(d, l, s) _SafeStrCpy(d, l, s, __FILE__, __LINE__)

/**
Detect if we are running a test system by querying the SMBIOS vendor string.
Expand Down Expand Up @@ -302,3 +349,16 @@ EFI_STATUS HashFile(IN CONST EFI_FILE_HANDLE Root, IN CONST CHAR16* Path, OUT UI
@retval EFI_BUFFER_TOO_SMALL The output buffer is too small to hold the result.
**/
EFI_STATUS Utf8ToUcs2(IN CONST CHAR8* Utf8String, OUT CHAR16* Ucs2String, IN CONST UINTN Ucs2StringSize);

/**
Fix the casing of a path to match the actual case of the referenced elements.
@param[in] Root A handle to the root partition.
@param[in/out] Path The path to update.
@retval EFI_SUCCESS The path was successfully updated.
@retval EFI_INVALID_PARAMETER One or more of the input parameters are invalid.
@retval EFI_OUT_OF_RESOURCES A memory allocation error occurred.
@retval EFI_NOT_FOUND One of the path elements was not found.
**/
EFI_STATUS SetPathCase(CONST EFI_FILE_HANDLE Root, CHAR16* Path);
Binary file added chainload_test.7z
Binary file not shown.
Loading

0 comments on commit aae9821

Please sign in to comment.