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/shfor single-user root access) - Under Secure Boot, GRUB2 verifies kernel signature with
linuxeficommand (notlinux) - 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
-
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 ofblkid /dev/sda2. -
Custom Menu Entry: Add a custom entry to
/etc/grub.d/40_customthat boots the current kernel withsystemd.unit=rescue.target(rescue mode without needing single-user). Regenerate grub.cfg withgrub2-mkconfigand verify the entry appears. -
Rescue Lab: In a QEMU VM, deliberately corrupt
grub.cfgby overwriting it with garbage. Boot the VM and practice recovering from thegrub rescue>prompt using onlyls,insmod,set, andnormalcommands. -
Module Analysis: Examine the modules embedded in an existing GRUB2 core.img with
grub2-mkimage --listequivalent. Determine which modules are embedded vs loaded from/boot/grub2/. Rebuild core.img adding theluks2module for LUKS2 disk encryption support. -
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 usingsystemd-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