Skip to content

Commit

Permalink
fallback: Detect and break out of reboot loops
Browse files Browse the repository at this point in the history
The firmware on some Acer machines (and maybe others) always resets the
boot entries and BootOrder variable to what was defined in the firmware
setup program, overriding any external changes (including the changes
made by fallback).

Before shim cared about TPMs this was not a problem in practice, as
fallback would create and chain-load a boot entry for the OS on every
boot. However, since commit 431b8a2 the system is restarted if a TPM
is detected on the system, triggering an infinite reboot loop in systems
with such firmware. This is a known problem which has been previously
reported on rhboot#128

More recently, the problem has been addressed by commit a5db51a,
which presents a screen with a countdown to the user, where they can
interrupt boot and choose to have fallback always chain-load the new
entry instead of restarting the system, to break out of the reboot loop.
While this solution works, it has a few shortcomings:

 1. It makes an otherwise glitch-free boot process not smooth anymore.
 2. The message presented is not accessible / potentially scary for
    non-technical users: if they press a key to interrupt the boot
    process, the meaning of each option is not really clear for users
    not familiar with how shim and fallback work.
 3. The whole experience is made a bit worse by the fact that after
    selecting "Continue boot" / "Always continue boot", the screen will
    remain frozen until something else draw on the framebuffer. If GRUB
    is configured to be quiet, for a glitch-free boot, this may last
    several seconds until the kernel has started and loaded the
    manufacturer logo from BGRT, which gives the impression that the
    whole boot process froze.
 4. This Boot Option Restoration screen overwrites all the debug
    information printed before it is displayed, essentially neutering
    FALLBACK_VERBOSE or SHIM_VERBOSE and making it impossible to enable
    debug without rebuilding fallback.

This commit tries to automatically detect and break out of the reboot
loop without requiring any user interaction. To achieve this, a boot
counter is stored in an EFI variable and incremented every time fallback
is about to reboot the system. If the counter ever reaches a maximum
value configurable at build time (currently default to 3), another EFI
variable is set to tell fallback to always chain-load the new entry
(FB_NO_REBOOT, to make is backwards compatible with the previous
solution). The counter is then reset when shim is started and knows it
is not going to load fallback.

Fixes: rhboot#418

Signed-off-by: João Paulo Rechi Vita <[email protected]>
  • Loading branch information
jprvita committed Sep 21, 2021
1 parent 5a5c37a commit b5bea21
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 1 deletion.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ ifneq ($(origin FALLBACK_VERBOSE_WAIT), undefined)
CFLAGS += -DFALLBACK_VERBOSE_WAIT=$(FALLBACK_VERBOSE_WAIT)
endif

ifneq ($(origin FALLBACK_MAX_BOOT_ATTEMPTS), undefined)
CFLAGS += -DFALLBACK_MAX_BOOT_ATTEMPTS=$(FALLBACK_MAX_BOOT_ATTEMPTS)
else
CFLAGS += -DFALLBACK_MAX_BOOT_ATTEMPTS=3
endif

all: confcheck $(TARGETS)

confcheck:
Expand Down
41 changes: 40 additions & 1 deletion fallback.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
#include "shim.h"

#define BOOT_COUNT L"FB_BOOT_COUNT"
#define NO_REBOOT L"FB_NO_REBOOT"

EFI_LOADED_IMAGE *this_image = NULL;
Expand Down Expand Up @@ -1026,6 +1027,31 @@ try_start_first_option(EFI_HANDLE parent_image_handle)
return efi_status;
}

static UINT32
get_boot_count(void)
{
EFI_STATUS efi_status;
UINT32 boot_count;
UINTN size = sizeof(UINT32);

efi_status = RT->GetVariable(BOOT_COUNT, &SHIM_LOCK_GUID,
NULL, &size, &boot_count);
if (!EFI_ERROR(efi_status)) {
return boot_count;
}
return 0;
}

static EFI_STATUS
set_boot_count(UINT32 boot_count)
{
return RT->SetVariable(BOOT_COUNT, &SHIM_LOCK_GUID,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(UINT32), &boot_count);
}

static UINT32
get_fallback_no_reboot(void)
{
Expand All @@ -1041,7 +1067,6 @@ get_fallback_no_reboot(void)
return 0;
}

#ifndef FALLBACK_NONINTERACTIVE
static EFI_STATUS
set_fallback_no_reboot(void)
{
Expand All @@ -1055,6 +1080,7 @@ set_fallback_no_reboot(void)
return efi_status;
}

#ifndef FALLBACK_NONINTERACTIVE
static int
draw_countdown(void)
{
Expand Down Expand Up @@ -1121,6 +1147,7 @@ EFI_STATUS
efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
EFI_STATUS efi_status;
UINT32 boot_count = 0;

InitializeLib(image, systab);

Expand Down Expand Up @@ -1153,6 +1180,17 @@ efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
VerbosePrint(L"tpm not present, starting the first image\n");
try_start_first_option(image);
} else {
boot_count = get_boot_count();
boot_count++;

VerbosePrint(L"fallback boot attempt number %d\n", boot_count);

if (boot_count >= FALLBACK_MAX_BOOT_ATTEMPTS) {
VerbosePrint(L"reboot loop detected: booted fallback %d times in a row, setting %s\n",
FALLBACK_MAX_BOOT_ATTEMPTS, NO_REBOOT);
set_fallback_no_reboot();
}

if (get_fallback_no_reboot() == 1) {
VerbosePrint(L"%s is set, starting the first image\n",
NO_REBOOT);
Expand Down Expand Up @@ -1180,6 +1218,7 @@ efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
}

console_print(L"Reset System\n");
set_boot_count(boot_count);

if (get_fallback_verbose()) {
int fallback_verbose_wait = 500000; /* default to 0.5s */
Expand Down
5 changes: 5 additions & 0 deletions shim.c
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,11 @@ EFI_STATUS init_grub(EFI_HANDLE image_handle)
EFI_STATUS efi_status;
int use_fb = should_use_fallback(image_handle);

if (!use_fb)
/* If we are not going through fallback,
* clear the fallback reboot counter */
LibDeleteVariable(L"FB_BOOT_COUNT", &SHIM_LOCK_GUID);

efi_status = start_image(image_handle, use_fb ? FALLBACK :second_stage);
if (efi_status == EFI_SECURITY_VIOLATION ||
efi_status == EFI_ACCESS_DENIED) {
Expand Down

0 comments on commit b5bea21

Please sign in to comment.