Skip to content

GRUB2 — Grand Unified Bootloader Version 2

Technical Overview

GRUB2 (GRand Unified Bootloader 2) is the standard bootloader for Linux systems. It bridges the gap between firmware (BIOS or UEFI) and the Linux kernel, providing: hardware abstraction over different firmware environments, a module-loading system for filesystems and device drivers, a configuration language for constructing boot menus, and a rescue environment for recovery.

GRUB2 is architecturally quite different from its predecessor (GRUB Legacy / GRUB 0.97). It supports dynamic module loading, filesystem drivers as loadable modules, scripting with variables and conditionals in grub.cfg, two distinct deployment architectures (BIOS core.img in MBR gap vs UEFI EFI application), and a formal multiboot protocol for loading kernels.

Understanding GRUB2 at the engineering level means understanding its two-phase structure, the module system, how grub.cfg is parsed and executed, and the failure modes that bring administrators to the grub rescue> prompt at 3am.

Prerequisites

  • BIOS and MBR concepts (see 01-bios-and-post.md)
  • UEFI and ESP (see 02-uefi.md)
  • Secure Boot and shim (see 03-secure-boot.md)
  • Linux kernel image formats (see 05-kernel-decompression.md)

Historical Context

GRUB (version 0.97, commonly called GRUB Legacy) was developed by Erich Boleyn in 1995 and maintained by the GNU Project. It introduced the concept of a bootloader that could understand filesystems, read configuration files, and boot multiple operating systems with a menu. Before GRUB, LILO (Linux Loader) required rebuilding the bootloader's sector map every time the kernel moved on disk.

GRUB Legacy had significant limitations: no module system, monolithic design, poor support for modern filesystems (no ext4, no Btrfs, no LVM), and no UEFI support. GRUB2 was a complete rewrite, initiated around 2002, that addressed all of these. GRUB2 is now the default bootloader on Fedora, Ubuntu, Debian, openSUSE, and most other major Linux distributions.

GRUB2 itself is GNU software (GPL v3), maintained at https://www.gnu.org/software/grub/.

GRUB2 Architecture: BIOS vs UEFI Deployment

GRUB2 has fundamentally different deployment architectures depending on firmware:

GRUB2 Deployment: BIOS Mode

  Disk:
  +------------+--------+------------------+----------------+
  | MBR        | MBR    | /boot partition  | root partition |
  | (boot.img) | gap    | (ext4/xfs)       |                |
  |            |core.img|                  |                |
  +------------+--------+------------------+----------------+
  LBA 0        1  ...63  LBA 2048

  boot.img (446 bytes, fits in MBR bootstrap code area):
    - Loaded by BIOS at 0x7C00
    - Reads core.img from hardcoded disk location
    - Jumps to core.img diskboot.img section

  core.img (assembled from: diskboot.img + lzma.mod +
            biosdisk.mod + part_msdos.mod +
            part_gpt.mod + ext2.mod + normal.mod):
    - Self-contained, installed in MBR gap (sectors 1–63)
    - Can read filesystems to load modules from /boot/grub2/
    - Decompresses and loads normal.mod
    - normal.mod reads grub.cfg and presents menu

  GRUB2 BIOS Deployment: BIOS Mode
  +----------+
  | BIOS     |
  +----+-----+
       | loads
       v
  +----------+   reads core.img   +----------+
  | boot.img | ─────────────────> | core.img |
  | MBR      |                    | MBR gap  |
  +----------+                    +----+-----+
                                       | loads modules from
                                       v
                                  +----------+
                                  |/boot/grub2/|
                                  | modules   |
                                  +----------+
GRUB2 Deployment: UEFI Mode

  Disk:
  +-----+-------------------+------------------+
  | GPT | ESP               | root partition   |
  |     | /boot/efi (FAT32) |                  |
  |     | /EFI/fedora/      |                  |
  |     |   shimx64.efi     |                  |
  |     |   grubx64.efi     |                  |
  |     |   grub.cfg        |                  |
  +-----+-------------------+------------------+

  grubx64.efi = single EFI application containing:
    - GRUB2 core (no staging needed — UEFI has no size limit)
    - All required modules statically linked in
    - (or memdisk with modules embedded)

  UEFI calls ExitBootServices after GRUB loads kernel.
  No "MBR gap" concept needed in UEFI mode.

  UEFI Mode Deployment:
  +----------+
  | UEFI     |
  +----+-----+
       | loads shimx64.efi (Microsoft-signed)
       v
  +----------+
  | shim     | verifies grubx64.efi signature
  +----+-----+
       | loads
       v
  +----------+
  |grubx64.efi|  reads /boot/grub2/grub.cfg
  +----+-----+
       | loads
       v
  +---------+
  | kernel  |
  +---------+

GRUB2 Module System

GRUB2 is highly modular. The core image contains minimal code; virtually all functionality (filesystem drivers, device drivers, cryptographic functions) are loadable modules (.mod files).

Module categories: - Filesystem: ext2, btrfs, xfs, fat, iso9660, ntfs, zfs - Partition table: part_msdos, part_gpt - Device support: biosdisk, ahci, nvme, usb_keyboard - Network: net, tftp, http, pxe - Crypto: gcry_sha256, gcry_rsa, luks, luks2 - Compression: lzma, gzio, xzio - Boot protocols: linux, multiboot, multiboot2, chainloader - UI: normal, menu, gfxterm, png, jpeg

Modules live in /boot/grub2/i386-pc/ (BIOS) or /boot/grub2/x86_64-efi/ (UEFI).

grub.cfg Syntax and Structure

The grub.cfg file is a script interpreted by GRUB2's configuration language. It is typically at /boot/grub2/grub.cfg (Fedora/RHEL) or /boot/grub/grub.cfg (Debian/Ubuntu).

Key directives:

# grub.cfg example — annotated

# Search for the root filesystem by UUID
search --no-floppy --fs-uuid --set=root a1b2c3d4-e5f6-7890-abcd-ef1234567890

# Set default menu entry and timeout
set default="0"
set timeout=5
set timeout_style=menu

# Load graphics support
insmod all_video
insmod gfxterm
terminal_output gfxterm

# Load environment block for saved defaults
load_env

# Main menu entry for the current kernel
menuentry 'Fedora Linux (6.7.4-200.fc39.x86_64) 39 (Workstation Edition)' \
    --class fedora --class gnu-linux --class gnu --class os \
    --unrestricted $menuentry_id_option \
    'gnulinux-6.7.4-200.fc39.x86_64-advanced-a1b2c3d4' {
        # Load Linux kernel
        insmod gzio
        insmod part_gpt
        insmod ext2

        # Set root to boot partition
        search --no-floppy --fs-uuid --set=root \
            a1b2c3d4-e5f6-7890-abcd-ef1234567890

        # Specify kernel image and command line
        linux   /vmlinuz-6.7.4-200.fc39.x86_64 \
                root=/dev/mapper/fedora-root \
                ro \
                quiet \
                rhgb \
                rd.luks.uuid=luks-xxxx \
                rd.lvm.lv=fedora/root

        # Specify initial RAM filesystem
        initrd  /initramfs-6.7.4-200.fc39.x86_64.img
}

# Recovery/rescue entry
menuentry 'Fedora Linux (6.7.4-200.fc39.x86_64) — rescue mode' \
    --class fedora --class gnu-linux {
        linux   /vmlinuz-6.7.4-200.fc39.x86_64 \
                root=/dev/mapper/fedora-root \
                ro \
                single
        initrd  /initramfs-6.7.4-200.fc39.x86_64.img
}

# Chainload Windows Boot Manager
menuentry 'Windows 11' {
        insmod part_gpt
        insmod fat
        search --no-floppy --fs-uuid --set=root ABCD-EF01
        chainloader /EFI/Microsoft/Boot/bootmgfw.efi
}

grub.cfg variables: - $root: Current root device (set by search command) - $prefix: Path to GRUB2 modules directory - $grub_platform: efi or pc - $grub_cpu: x86_64 or i386 - $default: Index of default menu entry - $timeout: Menu timeout in seconds

grub-install and grub-mkconfig

grub-install: Installs GRUB2 to a device. For BIOS, writes boot.img to MBR and core.img to the MBR gap. For UEFI, copies grubx64.efi to ESP and registers a UEFI boot entry.

# BIOS installation to first disk
grub2-install /dev/sda
# or: grub-install --target=i386-pc /dev/sda

# UEFI installation
grub2-install --target=x86_64-efi \
              --efi-directory=/boot/efi \
              --bootloader-id=fedora

# With module embedding (for encrypted /boot)
grub2-install --target=x86_64-efi \
              --efi-directory=/boot/efi \
              --modules="gcry_sha256 gcry_rsa luks2 cryptodisk"

grub-mkconfig (Fedora: grub2-mkconfig): Generates grub.cfg by running scripts in /etc/grub.d/:

# Generate and install grub.cfg
grub2-mkconfig -o /boot/grub2/grub.cfg       # Fedora/RHEL (BIOS)
grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg  # Fedora/RHEL (UEFI)
update-grub                                   # Debian/Ubuntu (wrapper)

# /etc/grub.d/ scripts (executed in numeric order):
# 00_header      — sets timeout, default, theme
# 10_linux       — generates entries for installed kernels
# 20_linux_xen   — Xen hypervisor entries
# 30_os-prober   — detects other OSes (Windows, other Linux)
# 40_custom      — user-defined custom entries
# 41_custom      — additional custom entries

Configuration settings go in /etc/default/grub:

GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="Fedora"
GRUB_DEFAULT=saved
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="rhgb quiet rd.luks.uuid=... rd.lvm.lv=fedora/root"
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_CRYPTODISK=y    # enable disk encryption support in GRUB

GRUB Environment Block (grubenv)

The GRUB environment block is a 1024-byte file (/boot/grub2/grubenv) used to persist variables across reboots. It enables features like "boot the last selected entry" and "automatically boot previous kernel on update failure."

# View grubenv contents
grub2-editenv list

# Set the default boot entry to saved
# (requires GRUB_DEFAULT=saved in /etc/default/grub)
grub2-set-default 0

# After a kernel update, test new kernel:
# If new kernel fails to boot, GRUB can fall back to previous entry
# using grub2-reboot or by the grubby --set-default mechanism

# grub2-reboot: set one-shot next boot entry
grub2-reboot 0

The grubenv file format is: magic header # GRUB2 Environment Block followed by null-padded key=value pairs filling exactly 1024 bytes.

Multiboot2 Protocol

GRUB2 can load non-Linux kernels using the Multiboot2 specification. A Multiboot2-compliant kernel contains a header with a magic number (0xE85250D6) that GRUB2 recognizes. GRUB2 then loads the kernel according to its load segments, builds a Multiboot2 information structure (containing memory map, module addresses, command line, ACPI tables), and jumps to the kernel entry point.

This is the boot protocol used by hobby OS projects and microkernels. Linux does not use Multiboot2 — it uses its own boot protocol (Documentation/x86/boot.rst).

Multiboot2 Header (must be in first 32KB of kernel):

  +------------------------+
  | Magic: 0xE85250D6      |  4 bytes
  | Architecture: 0 (x86) |  4 bytes
  | Header length          |  4 bytes
  | Checksum               |  4 bytes
  | Tags (variable)        |
  |   Tag type             |  2 bytes
  |   Tag flags            |  2 bytes
  |   Tag size             |  4 bytes
  |   Tag data             |  variable
  +------------------------+

GRUB2 Rescue Mode

When GRUB2 cannot find its configuration or critical modules, it drops to a minimal rescue shell with only basic commands:

grub rescue>

The rescue shell has access to: ls, set, insmod, normal. The recovery procedure:

# Step 1: Find available devices
grub rescue> ls
(hd0) (hd0,gpt1) (hd0,gpt2) (hd0,gpt3)

# Step 2: Search for GRUB directory
grub rescue> ls (hd0,gpt2)/
# Look for /grub2/ or /grub/ directory

# Step 3: Set prefix and root
grub rescue> set root=(hd0,gpt2)
grub rescue> set prefix=(hd0,gpt2)/grub2

# Step 4: Load normal module
grub rescue> insmod normal

# Step 5: Launch normal GRUB menu
grub rescue> normal

The slightly richer grub> prompt (not rescue) has access to all loaded modules and the full command set.

GRUB2 Security

Password protection: Restrict access to edit boot entries or enter the GRUB shell:

# /etc/grub.d/40_custom:
set superusers="admin"
password_pbkdf2 admin grub.pbkdf2.sha512.10000.<hash>

# Generate hash:
grub2-mkpasswd-pbkdf2

With superusers set, menuentry must declare --unrestricted or --users admin to control access.

Signed configurations with GRUB2 Secure Boot: When GRUB2 runs under Secure Boot, it can enforce signature verification of grub.cfg (preventing an attacker with ESP write access from modifying the config to add malicious kernel arguments or load arbitrary code):

# Sign grub.cfg
gpg --detach-sign --batch --yes grub.cfg
# grub.cfg.sig must be present alongside grub.cfg

# GRUB2 with GRUB_ENABLE_SIGN=y will verify .sig before reading .cfg

This is the mitigation for the BootHole vulnerability (CVE-2020-10713) at the GRUB2 level — even if an attacker modifies grub.cfg, GRUB won't execute it without a valid signature.

systemd-boot as Alternative

systemd-boot (formerly gummiboot) is a minimal EFI boot manager that is simpler and faster than GRUB2 but less capable. It is the default on systemd-based distributions that don't need complex multiboot support.

/boot/efi/
  EFI/
    BOOT/
      BOOTX64.EFI         ← systemd-boot EFI app
    systemd/
      systemd-bootx64.efi ← same, canonical location
  loader/
    loader.conf           ← global configuration
    entries/
      linux.conf          ← boot entry for main system
      linux-fallback.conf ← fallback entry

loader.conf:

default   linux.conf
timeout   5
console-mode max
editor    no          ← disable kernel cmdline editing (security)

Entry file (entries/linux.conf):

title   Arch Linux
linux   /vmlinuz-linux
initrd  /initramfs-linux.img
options root=UUID=... rw quiet

systemd-boot has no filesystem driver beyond FAT32 (ESP only) and no scripting. Kernels and initramfs must live on the ESP. For most single-OS systems on modern hardware, this is fine and faster to configure.

bootctl manages systemd-boot:

bootctl install          # install to ESP, register UEFI entry
bootctl update           # update systemd-boot binary
bootctl status           # show boot entries and status
bootctl list             # list entries

EFI Stub (Direct Kernel Boot)

The Linux kernel can be compiled with CONFIG_EFI_STUB=y, making the kernel image itself a valid EFI application. UEFI can then load the kernel directly, bypassing GRUB entirely.

# Kernel as EFI app — add UEFI boot entry pointing directly to kernel
efibootmgr --create --disk /dev/sda --part 1 \
  --loader /vmlinuz-linux \
  --label "Linux direct" \
  --unicode 'root=UUID=... rw initrd=\initramfs-linux.img'

The kernel's EFI stub reads the command line from the UEFI load option data and the initrd path from the initrd= parameter. The initrd must be on the ESP (FAT32) since there is no filesystem driver before the kernel runs.

Combining EFI stub with unified kernel images (UKIs) is the modern direction — see below.

Common Rescue Procedures

Scenario 1: grub.cfg not found (fresh install)

# Boot to live USB, chroot:
mount /dev/sda2 /mnt
mount /dev/sda1 /mnt/boot/efi   # ESP
mount --bind /dev /mnt/dev
mount --bind /proc /mnt/proc
mount --bind /sys /mnt/sys
mount --bind /sys/firmware/efi/efivars /mnt/sys/firmware/efi/efivars
chroot /mnt
grub2-install --target=x86_64-efi --efi-directory=/boot/efi
grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg

Scenario 2: Broken UEFI entry after disk move

# Delete old entries:
efibootmgr --bootnum 0001 --delete-bootnum
# Reinstall:
grub2-install --target=x86_64-efi --efi-directory=/boot/efi \
              --bootloader-id=fedora

Scenario 3: Kernel panic — boot previous kernel

# At GRUB menu: press E to edit, change kernel version in 'linux' line
# Or permanently:
grubby --set-default /boot/vmlinuz-<previous-version>

Production Examples

Encrypted root with GRUB2: When /boot is on an encrypted LUKS partition, GRUB2 must decrypt the partition before reading the kernel. This requires GRUB_ENABLE_CRYPTODISK=y and embedding the cryptodisk, luks2, gcry_sha256 modules in core.img. The passphrase is entered once at GRUB time (to decrypt /boot) and again by the initramfs (to open the root volume) — a usability issue addressed by TPM-based unlocking.

PXE Boot with GRUB2: GRUB2's grub-mknetdir creates a PXE-bootable GRUB2 image that loads grub.cfg from a TFTP server. This is used in large-scale provisioning systems (Foreman, Cobbler) to centrally manage boot menus for hundreds of servers.

Rescue from dracut emergency shell: If the initramfs cannot mount root, it drops to a dracut emergency shell. From there, manual device setup + switch_root allows recovery without full GRUB reinstall.

Debugging Notes

error: unknown filesystem: GRUB cannot read the filesystem where it expects grub.cfg. The module for that filesystem is not embedded in core.img. Rebuild with grub-install --modules=... to include the required module.

error: file not found on kernel/initrd: The UUID in grub.cfg no longer matches the filesystem (after disk replacement or clone). Update via grub-mkconfig or manually edit the UUID in grub.cfg.

GRUB menu not showing: GRUB_TIMEOUT=0 or GRUB_TIMEOUT_STYLE=hidden hides the menu. Hold Shift (BIOS) or Escape (UEFI) during boot to force the menu to appear.

Serial console in GRUB: Add to /etc/default/grub:

GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"

Security Implications

  • grub.cfg is world-readable on the ESP — never put secrets in it
  • Without password protection, anyone with physical access can edit kernel parameters (e.g., add init=/bin/sh for single-user root access)
  • Under Secure Boot, GRUB2 verifies kernel signature with linuxefi command (not linux)
  • GRUB2 CVE-2020-10713 (BootHole): all GRUB2 binaries pre-2.06 should be in dbx

Performance Implications

  • GRUB2 adds 1–3 seconds to boot time (module loading, filesystem init, menu timeout)
  • Embedded modules in core.img reduce module load time (important for encrypted boot)
  • Compiling fewer modules into the EFI image reduces grubx64.efi size and parse time
  • systemd-boot is measurably faster (~200ms vs ~1000ms for GRUB2 with menu)

Failure Modes

MBR gap collision: If a disk tool (fdisk, parted) creates a partition starting at sector 1 or 2 (normal is 2048), it will overwrite core.img. Always leave the MBR gap (sectors 1–2047) free on BIOS systems.

grubenv corruption: If grubenv is corrupted (e.g., power loss during write), GRUB cannot load the saved default. Recovery: grub2-editenv create to recreate an empty grubenv.

Wrong GRUB version for firmware: Installing grub2-efi on a BIOS-booted system or grub2-pc on a UEFI-booted system. Check with [ -d /sys/firmware/efi ] && echo UEFI || echo BIOS.

Modern Usage and Future Directions

GRUB2 remains dominant on Linux but is increasingly being supplemented or replaced:

Unified Kernel Images (UKIs): A UKI is a single EFI binary combining the kernel, initramfs, kernel command line, and OS release info, signed as a unit. systemd-stub (stub EFI code from systemd) wraps the kernel + initramfs into a single PE/COFF EFI application. This eliminates the need for GRUB2 in many scenarios and simplifies the Secure Boot chain (one signature covers kernel + initramfs + cmdline).

# Build a UKI with ukify (systemd 253+)
ukify build \
  --linux=/boot/vmlinuz-linux \
  --initrd=/boot/initramfs-linux.img \
  --cmdline="root=UUID=... rw quiet" \
  --output=/boot/efi/EFI/Linux/linux.efi

Secure Boot Automation: The sbctl tool automates the creation of custom Secure Boot keys, signing of EFI binaries, and NVRAM enrollment — providing a GRUB2-independent path to a fully custom Secure Boot setup.

Exercises

  1. grub.cfg Audit: On a running system, examine /boot/grub2/grub.cfg. Identify: the root device UUID, all kernel versions available, the default boot entry, and any rescue entries. Manually verify that the UUID matches the output of blkid /dev/sda2.

  2. Custom Menu Entry: Add a custom entry to /etc/grub.d/40_custom that boots the current kernel with systemd.unit=rescue.target (rescue mode without needing single-user). Regenerate grub.cfg with grub2-mkconfig and verify the entry appears.

  3. Rescue Lab: In a QEMU VM, deliberately corrupt grub.cfg by overwriting it with garbage. Boot the VM and practice recovering from the grub rescue> prompt using only ls, insmod, set, and normal commands.

  4. Module Analysis: Examine the modules embedded in an existing GRUB2 core.img with grub2-mkimage --list equivalent. Determine which modules are embedded vs loaded from /boot/grub2/. Rebuild core.img adding the luks2 module for LUKS2 disk encryption support.

  5. systemd-boot Migration: On a UEFI system currently using GRUB2, install systemd-boot alongside (not replacing) GRUB2 using bootctl install --path=/boot/efi/esp-test. Create a loader entry for the current kernel. Test booting via the new entry from the UEFI boot menu. Compare boot times using systemd-analyze.

References

  • GNU GRUB Manual: https://www.gnu.org/software/grub/manual/grub/
  • GRUB2 source: https://git.savannah.gnu.org/git/grub.git
  • BootHole: https://eclypsium.com/research/boothole/
  • GRUB2 Multiboot2 specification: https://www.gnu.org/software/grub/manual/multiboot2/
  • systemd-boot documentation: https://systemd.io/BOOT_LOADER_SPECIFICATION/
  • UKI specification: https://systemd.io/UNIFIED_KERNEL_IMAGES/
  • grubby man page — Red Hat's kernel management tool
  • Linux kernel Documentation/x86/boot.rst — Linux boot protocol
  • Rod Smith, "Managing EFI Boot Loaders for Linux": http://www.rodsbooks.com/efi-bootloaders/
  • sbctl: https://github.com/Foxboron/sbctl