Skip to content

Commit

Permalink
Warn users if they are using the buggy AMI NTFS UEFI driver
Browse files Browse the repository at this point in the history
* The AMI UEFI NTFS driver (v0x10000) can misread data and may produce unwarranted
  MD5 checksum errors, per https://github.com/pbatard/AmiNtfsBug.
* Even as we do use a large read buffer to try to alleviate the issue, it is still
  possible that, depending on how the media was created, people may trigger the bug
  so we now fetch the name and version of the file system driver used to service the
  partition we read files from, and produce a warning if it's the AMI NTFS one.
* Also make global variables more explicit by prefixing them with 'g'.
  • Loading branch information
pbatard committed Mar 13, 2024
1 parent 513d05a commit be97c5e
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 88 deletions.
2 changes: 2 additions & 0 deletions Md5SumPkg.inf
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
gEfiSmbios3TableGuid

[Protocols]
gEfiDiskIoProtocolGuid
gEfiDiskIo2ProtocolGuid
gEfiLoadedImageProtocolGuid
gEfiSimpleFileSystemProtocolGuid
gEfiUnicodeCollationProtocolGuid
Expand Down
48 changes: 28 additions & 20 deletions src/boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@

#include "boot.h"

/* Copy of the main Image Handle */
EFI_HANDLE gMainImageHandle = NULL;

/*
* When performing tests with GitHub Actions, we want to remove all
* colour formatting as well force shutdown on exit (to exit qemu)
* so we need a variable to tell us if we are running in test mode.
*/
BOOLEAN IsTestMode = FALSE;

/* Copy of the main Image Handle */
STATIC EFI_HANDLE MainImageHandle = NULL;
BOOLEAN gIsTestMode = FALSE;

/* Strings used for platform identification */
#if defined(_M_X64) || defined(__x86_64__)
Expand Down Expand Up @@ -65,8 +65,8 @@ STATIC EFI_STATUS GetRootHandle(
return EFI_INVALID_PARAMETER;

// Access the loaded image so we can open the current volume
Status = gBS->OpenProtocol(MainImageHandle, &gEfiLoadedImageProtocolGuid,
(VOID**)&LoadedImage, MainImageHandle,
Status = gBS->OpenProtocol(gMainImageHandle, &gEfiLoadedImageProtocolGuid,
(VOID**)&LoadedImage, gMainImageHandle,
NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (EFI_ERROR(Status))
return Status;
Expand All @@ -75,7 +75,7 @@ STATIC EFI_STATUS GetRootHandle(
// Open the the root directory on the boot volume
Status = gBS->OpenProtocol(LoadedImage->DeviceHandle,
&gEfiSimpleFileSystemProtocolGuid, (VOID**)&Volume,
MainImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
gMainImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (EFI_ERROR(Status))
return Status;

Expand Down Expand Up @@ -213,14 +213,14 @@ STATIC EFI_STATUS ExitProcess(

// If we have a bootloader to chain load, try to launch it
if (DevicePath != NULL) {
if (EFI_ERROR(Status) && Status != EFI_ABORTED && !IsTestMode) {
if (EFI_ERROR(Status) && Status != EFI_ABORTED && !gIsTestMode) {
// Ask the user if they want to continue, unless md5sum.txt could
// not be found, in which case continue boot right away.
if (Status != EFI_NOT_FOUND) {
SetText(TEXT_YELLOW);
// Give the user 1 hour to answer the question
gBS->SetWatchdogTimer(3600, 0x11D5, 0, NULL);
PrintCentered(L"Continue with boot? [y/N]", Console.Rows - 2);
PrintCentered(L"Continue with boot? [y/N]", gConsole.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') {
Expand All @@ -232,31 +232,31 @@ STATIC EFI_STATUS ExitProcess(
}
// Reset the watchdog to the default 5 minutes timeout and system code
gBS->SetWatchdogTimer(300, 0, 0, NULL);
Status = gBS->LoadImage(FALSE, MainImageHandle, DevicePath, NULL, 0, &ImageHandle);
Status = gBS->LoadImage(FALSE, gMainImageHandle, DevicePath, NULL, 0, &ImageHandle);
SafeFree(DevicePath);
if (Status == EFI_SUCCESS) {
if (RunCountDown)
CountDown(L"Continuing in", 3000);
if (!IsTestMode)
if (!gIsTestMode)
gST->ConOut->ClearScreen(gST->ConOut);
Status = gBS->StartImage(ImageHandle, NULL, NULL);
}
if (EFI_ERROR(Status)) {
SetTextPosition(0, Console.Rows / 2 + 1);
SetTextPosition(0, gConsole.Rows / 2 + 1);
PrintError(L"Could not launch original bootloader");
}
}

// If running in test mode, shut down QEMU
if (IsTestMode)
if (gIsTestMode)
ShutDown();

// Wait for a user keystroke as needed
#if !defined(EFI_DEBUG)
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]", gConsole.Rows - 2);
DefText();
gST->ConIn->Reset(gST->ConIn, FALSE);
gST->BootServices->WaitForEvent(1, &gST->ConIn->WaitForKey, &Index);
Expand Down Expand Up @@ -289,7 +289,7 @@ EFI_STATUS EFIAPI efi_main(
PROGRESS_DATA Progress = { 0 };

// Keep a global copy of the bootloader's image handle
MainImageHandle = BaseImageHandle;
gMainImageHandle = BaseImageHandle;

#if defined(_GNU_EFI)
InitializeLib(BaseImageHandle, SystemTable);
Expand All @@ -298,7 +298,7 @@ EFI_STATUS EFIAPI efi_main(
// Determine if we are running in test mode.
// Note that test mode is no less secure than regular mode.
// It only produces or removes extra onscreen output.
IsTestMode = IsTestSystem();
gIsTestMode = IsTestSystem();

InitConsole();

Expand All @@ -308,6 +308,14 @@ EFI_STATUS EFIAPI efi_main(
goto out;
}

// Detect if we are booting from an NTFS partition served by the buggy
// AMI NTFS driver and alert the user if that is the case.
if (!IsProblematicNtfsDriver(DeviceHandle)) {
PrintWarning(L"Buggy AMI NTFS driver detected!");
PrintWarning(L"This driver may produce unexpected checksum errors.");
PrintWarning(L"For details, see https://github.com/pbatard/AmiNtfsBug.");
}

// Look up the original boot loader for chain loading
UnicodeSPrint(LoaderPath, ARRAY_SIZE(LoaderPath),
L"\\efi\\boot\\boot%s_original.efi", Arch);
Expand All @@ -330,15 +338,15 @@ EFI_STATUS EFIAPI efi_main(
Progress.Type = (HashList.TotalBytes == 0) ? PROGRESS_TYPE_FILE : PROGRESS_TYPE_BYTE;
Progress.Maximum = (HashList.TotalBytes == 0) ? HashList.NumEntries : HashList.TotalBytes;
Progress.Message = L"Media validation";
Progress.YPos = Console.Rows / 2 - 3;
Progress.YPos = gConsole.Rows / 2 - 3;
InitProgress(&Progress);
SetText(TEXT_YELLOW);
if (!IsTestMode)
PrintCentered(L"[Press any key to cancel]", Console.Rows - 2);
if (!gIsTestMode)
PrintCentered(L"[Press any key to cancel]", gConsole.Rows - 2);
DefText();

// Set up the scroll section where we display individual file validation errors
Status = InitScrollSection(Console.Rows / 2 + 1, Console.Rows / 2 - 4);
Status = InitScrollSection(gConsole.Rows / 2 + 1, gConsole.Rows / 2 - 4);
if (EFI_ERROR(Status)) {
PrintError(L"Could not set up scroll section");
goto out;
Expand Down
44 changes: 31 additions & 13 deletions src/boot.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@

#include <Protocol/ComponentName.h>
#include <Protocol/ComponentName2.h>
#include <Protocol/DiskIo.h>
#include <Protocol/DiskIo2.h>
#include <Protocol/LoadedImage.h>

#include <Guid/FileInfo.h>
Expand All @@ -63,18 +65,21 @@
* Global variables
*/

/* Copy of the main Image Handle */
extern EFI_HANDLE gMainImageHandle;

/* Set to true when we are running the GitHub Actions tests */
extern BOOLEAN IsTestMode;
extern BOOLEAN gIsTestMode;

/* Dimensions of the UEFI text console */
typedef struct {
UINTN Cols;
UINTN Rows;
} CONSOLE_DIMENSIONS;
extern CONSOLE_DIMENSIONS Console;
extern CONSOLE_DIMENSIONS gConsole;

/* Incremental vertical position at which we display alert messages */
extern UINTN AlertYPos;
extern UINTN gAlertYPos;

/* SMBIOS vendor name used by GitHub Actions' qemu when running the tests */
#define TESTING_SMBIOS_NAME "GitHub Actions Test"
Expand Down Expand Up @@ -149,7 +154,7 @@ typedef UINT32 CHAR32;
#define BANNER_LINE_SIZE 79

/*
* Console colours we will be using
* gConsole colours we will be using
*/
#define TEXT_DEFAULT EFI_TEXT_ATTR(EFI_LIGHTGRAY, EFI_BLACK)
#define TEXT_REVERSED EFI_TEXT_ATTR(EFI_BLACK, EFI_LIGHTGRAY)
Expand All @@ -162,8 +167,8 @@ typedef UINT32 CHAR32;
/*
* Set and restore the console text colour
*/
#define SetText(attr) do { if (!IsTestMode) gST->ConOut->SetAttribute(gST->ConOut, (attr)); } while(0)
#define DefText() do { if (!IsTestMode) gST->ConOut->SetAttribute(gST->ConOut, TEXT_DEFAULT); } while(0)
#define SetText(attr) do { if (!gIsTestMode) gST->ConOut->SetAttribute(gST->ConOut, (attr)); } while(0)
#define DefText() do { if (!gIsTestMode) gST->ConOut->SetAttribute(gST->ConOut, TEXT_DEFAULT); } while(0)

/* Types of progress */
#define PROGRESS_TYPE_FILE 0
Expand All @@ -184,23 +189,23 @@ typedef struct {
/*
* Convenience macros to print informational, warning or error messages.
*/
#define PrintInfo(fmt, ...) do { SetTextPosition(0, AlertYPos++); \
#define PrintInfo(fmt, ...) do { SetTextPosition(0, gAlertYPos++); \
SetText(TEXT_WHITE); Print(L"[INFO]"); DefText(); \
Print(L" " fmt L"\n", ##__VA_ARGS__); } while(0)
#define PrintWarning(fmt, ...) do { SetTextPosition(0, AlertYPos++); \
#define PrintWarning(fmt, ...) do { SetTextPosition(0, gAlertYPos++); \
SetText(TEXT_YELLOW); Print(L"[WARN]"); DefText(); \
Print(L" " fmt L"\n", ##__VA_ARGS__); } while(0)
#define PrintError(fmt, ...) do { SetTextPosition(0, AlertYPos++); \
#define PrintError(fmt, ...) do { SetTextPosition(0, gAlertYPos++); \
SetText(TEXT_RED); Print(L"[FAIL]"); DefText(); \
Print(L" " fmt L": [%d] %r\n", ##__VA_ARGS__, (Status&0x7FFFFFFF), Status); } while (0)
#define PrintTest(fmt, ...) do { if (IsTestMode) Print(L"[TEST] " fmt L"\n", ##__VA_ARGS__); } while(0)
#define PrintTest(fmt, ...) do { if (gIsTestMode) Print(L"[TEST] " fmt L"\n", ##__VA_ARGS__); } while(0)

/* Convenience macro to position text on screen (when not running in test mode). */
#define SetTextPosition(x, y) do { if (!IsTestMode) gST->ConOut->SetCursorPosition(gST->ConOut, x, y);} while (0)
#define SetTextPosition(x, y) do { if (!gIsTestMode) gST->ConOut->SetCursorPosition(gST->ConOut, x, y);} while (0)

/* Convenience assertion macro */
#define P_ASSERT(f, l, a) do { if(!(a)) { Print(L"\n*** ASSERT FAILED: %a(%d): %a ***\n", f, l, #a); \
if (IsTestMode) ShutDown(); else Halt(); } } while(0)
if (gIsTestMode) ShutDown(); else Halt(); } } while(0)
#define V_ASSERT(a) P_ASSERT(__FILE__, __LINE__, a)

/*
Expand Down Expand Up @@ -366,6 +371,19 @@ STATIC __inline VOID _SafeStrCat(CHAR16* Destination, UINTN DestMax,
**/
BOOLEAN IsTestSystem(VOID);

/**
Detect if we are running of an NTFS partition with the buggy AMI NTFS file system
driver (See: https://github.com/pbatard/AmiNtfsBug)
@param[in] DeviceHandle A handle to the device running our boot image.
@retval TRUE The boot partition is serviced by the AMI NTFS driver.
@retval FALSE A non AMI NTFS file system driver is being used.
**/
BOOLEAN IsProblematicNtfsDriver(
CONST EFI_HANDLE DeviceHandle
);

/**
Parse a hash sum list file and populate a HASH_LIST structure from it.
Expand Down Expand Up @@ -429,7 +447,7 @@ EFI_STATUS Utf8ToUcs2(
);

/**
Console initialisation.
gConsole initialisation.
**/
VOID InitConsole(VOID);

Expand Down
Loading

0 comments on commit be97c5e

Please sign in to comment.