-
-
Notifications
You must be signed in to change notification settings - Fork 206
initrd: add boot partition preflight check and documentation #2073
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
MarkAtwood
wants to merge
1
commit into
linuxboot:master
Choose a base branch
from
MarkAtwood:fix/add-boot-partition-preflight-check
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| # Boot Partition Requirements | ||
|
|
||
| Heads has one hard, non-negotiable requirement about disk layout: | ||
| **`/boot` must be a separate, unencrypted partition.** | ||
|
|
||
| This is not a preference or a recommendation. It is the foundation of how | ||
| Heads works. If this requirement is not met, Heads' entire integrity model | ||
| is broken — and prior to this change, it would fail silently with confusing | ||
| "first boot setup" dialogs rather than a clear error. | ||
|
|
||
| ## Why | ||
|
|
||
| Heads runs from ROM (SPI flash), not from disk. When the machine powers on, | ||
| the ROM payload (Heads) runs before any disk is decrypted or mounted. Heads' | ||
| job is to: | ||
|
|
||
| 1. Mount `/boot` from the disk | ||
| 2. Check the GPG-signed hashes of every file in `/boot` | ||
| 3. Verify TPM PCR measurements match what was sealed at last-known-good state | ||
| 4. Only then `kexec` into the OS kernel | ||
|
|
||
| If `/boot` is part of an encrypted root (`/`), step 1 is impossible without | ||
| first decrypting the disk — but Heads has no key to do that until *after* | ||
| the integrity check. The circular dependency makes the security model | ||
| collapse entirely. | ||
|
|
||
| If `/boot` is a subdirectory of an unencrypted `/` (merged layout, no | ||
| separate partition), Heads has no way to identify the correct block device | ||
| to measure, and the `CONFIG_BOOT_DEV` variable — which the entire boot | ||
| integrity system depends on — is meaningless. | ||
|
|
||
| ## Requirements | ||
|
|
||
| | Property | Required value | Why | | ||
| |---|---|---| | ||
| | Separate partition | Yes | Heads identifies `/boot` by block device (`CONFIG_BOOT_DEV`), not by path | | ||
| | Unencrypted | Yes | Heads reads `/boot` before any LUKS container is opened | | ||
| | Filesystem | ext2, ext3, or ext4 | These are what the Heads initrd mounts | | ||
| | Size | ≥ 512 MB recommended | Kernels, initrds, and Xen images can be large | | ||
|
|
||
| ## Correct partition layout | ||
|
|
||
| ``` | ||
| /dev/sda1 512MB ext4 (unencrypted) → mounted as /boot by Heads | ||
| /dev/sda2 rest LUKS (encrypted) → root filesystem, swap, etc. | ||
| ``` | ||
|
|
||
| Inside the LUKS container, you can use LVM or btrfs subvolumes freely. | ||
| The only constraint is that `/boot` itself is outside the encrypted layer. | ||
|
|
||
| ## Incorrect layouts (will now error at boot) | ||
|
|
||
| ### Merged /boot (no separate partition) | ||
|
|
||
| ``` | ||
| /dev/sda1 LUKS → / (with /boot as a subdirectory) | ||
| ``` | ||
|
|
||
| Heads cannot mount this. `CONFIG_BOOT_DEV` has no valid value. | ||
|
|
||
| ### Encrypted /boot | ||
|
|
||
| ``` | ||
| /dev/sda1 512MB LUKS → /boot ← THIS DOES NOT WORK | ||
| /dev/sda2 rest LUKS → / | ||
| ``` | ||
|
|
||
| Heads cannot read an encrypted `/boot` before the integrity check. | ||
|
|
||
| ### Full-disk encryption with no /boot partition | ||
|
|
||
| Many installers (notably Debian 11+ default) do this. You **must** select | ||
| manual partitioning during OS install and create a separate unencrypted | ||
| `/boot` partition. | ||
|
|
||
| ## OS-specific notes | ||
|
|
||
| ### Debian / Ubuntu | ||
|
|
||
| The default installer in recent versions creates a single encrypted | ||
| partition. You must use **manual partitioning**: | ||
|
|
||
| - Create a 512MB primary partition, format as ext4, mount at `/boot`, | ||
| do **not** encrypt it | ||
| - Create a second partition, encrypt with LUKS, use for `/` (and optionally | ||
| LVM inside for swap/home) | ||
|
|
||
| ### Fedora / RHEL | ||
|
|
||
| The default partitioning scheme creates a separate unencrypted `/boot`. | ||
| Heads works with the defaults. Use ext4 for `/boot` rather than btrfs | ||
| (the Heads recovery shell does not support btrfs). | ||
|
|
||
| ### Qubes OS | ||
|
|
||
| The default Qubes partitioner creates a separate unencrypted `/boot`. | ||
| This is compatible with Heads without modification. | ||
|
|
||
| ### NixOS / Guix | ||
|
|
||
| These systems do not always default to a separate `/boot`. You must | ||
| explicitly declare a separate `/boot` partition in your configuration. | ||
|
|
||
| For NixOS, in `configuration.nix`: | ||
|
|
||
| ```nix | ||
| fileSystems."/boot" = { | ||
| device = "/dev/sda1"; | ||
| fsType = "ext4"; | ||
| }; | ||
| ``` | ||
|
|
||
| For Guix, in `config.scm`: | ||
|
|
||
| ```scheme | ||
| (file-system | ||
| (device "/dev/sda1") | ||
| (mount-point "/boot") | ||
| (type "ext4")) | ||
| ``` | ||
|
|
||
| ## Setting CONFIG_BOOT_DEV | ||
|
|
||
| Once the OS is installed with the correct layout, Heads needs to know which | ||
| partition is `/boot`. This is stored in the Heads config: | ||
|
|
||
| ```sh | ||
| # From the Heads recovery shell: | ||
| echo "export CONFIG_BOOT_DEV='/dev/sda1'" > /etc/config.user | ||
| ``` | ||
|
|
||
| Alternatively, use the GUI: **Options → Change Configuration Settings → | ||
| Change the Boot Device**. | ||
|
|
||
| This setting is saved into the ROM on the next firmware write, so it | ||
| persists across reboots without needing to set it again. | ||
|
|
||
| ## What happens if the requirement is not met | ||
|
|
||
| Starting with the commit that introduced `check-boot-partition`, Heads will | ||
| **refuse to boot** with a clear error message if: | ||
|
|
||
| - `CONFIG_BOOT_DEV` is not set | ||
| - `CONFIG_BOOT_DEV` does not exist as a block device | ||
| - `CONFIG_BOOT_DEV` is a LUKS-encrypted partition | ||
| - `CONFIG_BOOT_DEV` is the same device as `/` (merged layout) | ||
|
|
||
| Previously, these conditions produced confusing "first boot setup" dialogs | ||
| that gave no indication the disk layout was fundamentally incompatible. | ||
|
|
||
| ## Recovery | ||
|
|
||
| If you are stuck in the recovery shell because of a boot partition error: | ||
|
|
||
| 1. Check what partitions exist: `lsblk` or `fdisk -l` | ||
| 2. Try mounting candidate partitions: `mount /dev/sda1 /boot && ls /boot` | ||
| 3. If `/boot` contents are there (vmlinuz, initrd, grub.cfg), set the | ||
| variable: `echo "export CONFIG_BOOT_DEV='/dev/sda1'" > /etc/config.user` | ||
| 4. If there is no separate `/boot` partition, you will need to reinstall | ||
| the OS with the correct layout. There is no workaround. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| #!/bin/sh | ||
| # check-boot-partition: Validate that CONFIG_BOOT_DEV points to a real, | ||
| # separate, unencrypted block device partition — not a directory inside | ||
| # an already-mounted root filesystem. | ||
| # | ||
| # This is a hard architectural requirement of Heads: the integrity model | ||
| # depends on Heads (running from ROM) being able to mount, measure, and | ||
| # GPG-sign the contents of /boot before the OS ever runs. If /boot is | ||
| # part of an encrypted root, or is a subdirectory of /, that chain of | ||
| # custody is broken. | ||
| # | ||
| # Returns 0 if all checks pass. | ||
| # Calls `die` (from /etc/functions) on hard failures. | ||
| # Calls `warn` for conditions that are unusual but recoverable. | ||
| # | ||
| # Usage: check-boot-partition | ||
| # Called automatically from gui-init and init before mount_boot(). | ||
| # Can also be called from the recovery shell for diagnosis. | ||
|
|
||
| . /etc/functions | ||
| . /tmp/config | ||
|
|
||
| BOOT_CHECK_ERRORS=0 | ||
| BOOT_CHECK_WARNINGS=0 | ||
|
|
||
| _boot_error() { | ||
| BOOT_CHECK_ERRORS=$((BOOT_CHECK_ERRORS + 1)) | ||
| echo "!!! BOOT PARTITION ERROR: $*" >&2 | ||
| } | ||
|
|
||
| _boot_warn() { | ||
| BOOT_CHECK_WARNINGS=$((BOOT_CHECK_WARNINGS + 1)) | ||
| echo "WARNING: $*" >&2 | ||
| } | ||
|
|
||
| # ------------------------------------------------------------ | ||
| # CHECK 1: CONFIG_BOOT_DEV must be set | ||
| # ------------------------------------------------------------ | ||
| if [ -z "$CONFIG_BOOT_DEV" ]; then | ||
| _boot_error "CONFIG_BOOT_DEV is not set." | ||
| _boot_error "Heads requires a dedicated /boot partition." | ||
| _boot_error "Set CONFIG_BOOT_DEV in /etc/config.user, e.g.:" | ||
| _boot_error " echo \"export CONFIG_BOOT_DEV='/dev/sda1'\" > /etc/config.user" | ||
| fi | ||
|
|
||
| # ------------------------------------------------------------ | ||
| # CHECK 2: CONFIG_BOOT_DEV must be a block device | ||
| # ------------------------------------------------------------ | ||
| if [ -n "$CONFIG_BOOT_DEV" ]; then | ||
| if [ ! -e "$CONFIG_BOOT_DEV" ]; then | ||
| _boot_error "CONFIG_BOOT_DEV='$CONFIG_BOOT_DEV' does not exist." | ||
| _boot_error "Check that the drive is present and the device path is correct." | ||
| elif [ ! -b "$CONFIG_BOOT_DEV" ]; then | ||
| _boot_error "CONFIG_BOOT_DEV='$CONFIG_BOOT_DEV' exists but is not a block device." | ||
| _boot_error "Heads requires /boot to be a separate partition, not a directory." | ||
| _boot_error "If /boot is inside your root filesystem, Heads cannot provide" | ||
| _boot_error "boot integrity guarantees. You must repartition and reinstall." | ||
| fi | ||
| fi | ||
|
|
||
| # ------------------------------------------------------------ | ||
| # CHECK 3: CONFIG_BOOT_DEV must not be the same device as / | ||
| # ------------------------------------------------------------ | ||
| if [ -n "$CONFIG_BOOT_DEV" ] && [ -b "$CONFIG_BOOT_DEV" ]; then | ||
| ROOT_DEV="" | ||
| if grep -q ' / ' /proc/mounts 2>/dev/null; then | ||
| ROOT_DEV=$(grep ' / ' /proc/mounts | awk '{print $1}' | head -1) | ||
| fi | ||
|
|
||
| if [ -n "$ROOT_DEV" ] && [ "$ROOT_DEV" = "$CONFIG_BOOT_DEV" ]; then | ||
| _boot_error "CONFIG_BOOT_DEV='$CONFIG_BOOT_DEV' is the same device as /." | ||
| _boot_error "Heads requires /boot to be a SEPARATE partition from /." | ||
| _boot_error "A merged /boot means kernel and initrd updates are invisible" | ||
| _boot_error "to Heads until you manually re-sign, and Heads cannot detect" | ||
| _boot_error "tampering against a baseline it was never given." | ||
| fi | ||
| fi | ||
|
|
||
| # ------------------------------------------------------------ | ||
| # CHECK 4: CONFIG_BOOT_DEV must not be LUKS-encrypted | ||
| # ------------------------------------------------------------ | ||
| if [ -n "$CONFIG_BOOT_DEV" ] && [ -b "$CONFIG_BOOT_DEV" ]; then | ||
| BOOT_FSTYPE=$(blkid -o value -s TYPE "$CONFIG_BOOT_DEV" 2>/dev/null) | ||
|
|
||
| if [ "$BOOT_FSTYPE" = "crypto_LUKS" ]; then | ||
| _boot_error "CONFIG_BOOT_DEV='$CONFIG_BOOT_DEV' is a LUKS-encrypted partition." | ||
| _boot_error "Heads requires /boot to be UNENCRYPTED." | ||
| _boot_error "Heads runs from ROM and has no mechanism to decrypt LUKS before" | ||
| _boot_error "it reads the kernel, initrd, and grub config. An encrypted /boot" | ||
| _boot_error "makes the entire Heads integrity model impossible." | ||
| _boot_error "You must repartition: create a small (512MB) unencrypted ext4" | ||
| _boot_error "partition for /boot, and keep your root encrypted." | ||
| elif [ -z "$BOOT_FSTYPE" ]; then | ||
| _boot_warn "Could not determine filesystem type of $CONFIG_BOOT_DEV." | ||
| _boot_warn "Ensure it is a formatted, unencrypted ext2/ext3/ext4 partition." | ||
| fi | ||
| fi | ||
|
|
||
| # ------------------------------------------------------------ | ||
| # CHECK 5: Warn if /boot mount device mismatches CONFIG_BOOT_DEV | ||
| # ------------------------------------------------------------ | ||
| if grep -q ' /boot ' /proc/mounts 2>/dev/null; then | ||
| CURRENT_BOOT_DEV=$(grep ' /boot ' /proc/mounts | awk '{print $1}' | head -1) | ||
| if [ -n "$CONFIG_BOOT_DEV" ] && [ "$CURRENT_BOOT_DEV" != "$CONFIG_BOOT_DEV" ]; then | ||
| _boot_warn "/boot is currently mounted from $CURRENT_BOOT_DEV" | ||
| _boot_warn "but CONFIG_BOOT_DEV is set to $CONFIG_BOOT_DEV." | ||
| _boot_warn "These should match. Check your configuration." | ||
| fi | ||
| fi | ||
|
|
||
| # ------------------------------------------------------------ | ||
| # RESULT | ||
| # ------------------------------------------------------------ | ||
| if [ "$BOOT_CHECK_ERRORS" -gt 0 ]; then | ||
| echo "" >&2 | ||
| echo "===========================================================" >&2 | ||
| echo "HEADS BOOT PARTITION REQUIREMENT NOT MET ($BOOT_CHECK_ERRORS error(s))" >&2 | ||
| echo "" >&2 | ||
| echo "Heads requires a dedicated, separate, unencrypted partition" >&2 | ||
| echo "for /boot. This is not optional — it is the foundation of" >&2 | ||
| echo "Heads' integrity model." >&2 | ||
| echo "" >&2 | ||
| echo "See: https://osresearch.net/Boot-Partition-Requirements" >&2 | ||
| echo "===========================================================" >&2 | ||
| echo "" >&2 | ||
| die "Boot partition requirement not met. Cannot continue safely." | ||
| fi | ||
|
|
||
| if [ "$BOOT_CHECK_WARNINGS" -gt 0 ]; then | ||
| echo "Boot partition checks passed with $BOOT_CHECK_WARNINGS warning(s)." >&2 | ||
| fi | ||
|
|
||
| return 0 2>/dev/null || exit 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not true if using dvd live install, which detects legacy bios and creates unencrypted /boot