Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ $(info Verbose example: ./docker_repro.sh make BOARD=$(BOARD) V=1)
$(info ----------------------------------------------------------------------)
endif
$(info !!!!!! Build starts !!!!!!)
$(if $(BOARD),$(info COMMIT $(GIT_HASH) $(HEADS_GIT_VERSION)),)


# Timestamps should be in ISO format
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Configuration for building a coreboot ROM that works in
# the qemu emulator in graphical mode thanks to FBWhiptail
#
# TPM can be used with a qemu software TPM (TIS, 2.0).
export CONFIG_COREBOOT=y
export CONFIG_COREBOOT_VERSION=25.09
export CONFIG_LINUX_VERSION=6.1.8

CONFIG_COREBOOT_CONFIG=config/coreboot-qemu-tpm2.config
CONFIG_LINUX_CONFIG=config/linux-qemu.config

#Enable only one RESTRICTED/BASIC boot modes below to test them manually (we cannot inject config under QEMU (no internal flashing)
#export CONFIG_RESTRICTED_BOOT=y
export CONFIG_BASIC=y
export CONFIG_BASIC_USB_AUTOBOOT=y

#Enable HAVE_GPG_KEY_BACKUP to test GPG key backup drive (we cannot inject config under QEMU (no internal flashing))
#export CONFIG_HAVE_GPG_KEY_BACKUP=y


#On-demand hardware support (modules.cpio)
CONFIG_LINUX_USB=y
CONFIG_LINUX_E1000=y
#CONFIG_MOBILE_TETHERING=y
#Runtime on-demand additional hardware support (modules.cpio)
export CONFIG_LINUX_USB_COMPANION_CONTROLLER=y



#Modules packed into tools.cpio
ifeq "$(CONFIG_UROOT)" "y"
CONFIG_BUSYBOX=n
else
#Modules packed into tools.cpio
CONFIG_CRYPTSETUP2=y
CONFIG_FLASHPROG=y
CONFIG_FLASHTOOLS=y
CONFIG_GPG2=y
CONFIG_KEXEC=y
CONFIG_UTIL_LINUX=y
CONFIG_LVM2=y
CONFIG_MBEDTLS=y
CONFIG_PCIUTILS=y
#Runtime tools to write to MSR
CONFIG_MSRTOOLS=y
#Remote attestation support
# TPM2 requirements
CONFIG_TPM2_TSS=y
CONFIG_OPENSSL=y
#Remote Attestation common tools
CONFIG_POPT=y
CONFIG_QRENCODE=y
CONFIG_TPMTOTP=y
#HOTP based remote attestation for supported USB Security dongle
#With/Without TPM support
#CONFIG_HOTPKEY=y
#Nitrokey Storage admin tool (deprecated)
#CONFIG_NKSTORECLI=n
#GUI Support
#Console based Whiptail support(Console based, no FB):
#CONFIG_SLANG=y
#CONFIG_NEWT=y
#FBWhiptail based (Graphical):
CONFIG_CAIRO=y
CONFIG_FBWHIPTAIL=y
#Additional tools (tools.cpio):
#SSH server (requires ethernet drivers, eg: CONFIG_LINUX_E1000E)
#CONFIG_DROPBEAR=y
endif

#Runtime configuration
#Automatically boot if HOTP is valid
export CONFIG_AUTO_BOOT_TIMEOUT=5
#TPM2 requirements
export CONFIG_TPM2_TOOLS=y
export CONFIG_PRIMARY_KEY_TYPE=ecc
#TPM1 requirements
#export CONFIG_TPM=y
#Enable DEBUG output
export CONFIG_DEBUG_OUTPUT=y
export CONFIG_ENABLE_FUNCTION_TRACING_OUTPUT=y
#Enable TPM2 pcap output under /tmp
# When enabled, tpmr writes TPM2 command/response capture to /tmp/tpm0.pcap
# (inside the Heads runtime). This can be inspected with Wireshark to debug
# TPM interaction similarly to a TPM bus sniffer.
export CONFIG_TPM2_CAPTURE_PCAP=y
#Enable quiet mode: technical information logged under /tmp/debug.log
export CONFIG_QUIET_MODE=n
export CONFIG_BOOTSCRIPT=/bin/gui-init.sh
#text-based original init:
#export CONFIG_BOOTSCRIPT=/bin/generic-init.sh
export CONFIG_BOOT_REQ_HASH=n
export CONFIG_BOOT_REQ_ROLLBACK=n
export CONFIG_BOOT_RECOVERY_SERIAL="/dev/ttyS0"
export CONFIG_BOOT_KERNEL_ADD="console=ttyS0 console=tty systemd.zram=0 debug"
export CONFIG_BOOT_KERNEL_REMOVE="quiet rhgb splash"
export CONFIG_BOARD_NAME="qemu-coreboot-fbwhiptail-tpm2-basic-usb-autoboot"
#export CONFIG_FLASH_OPTIONS="flashprog --progress --programmer internal"

export CONFIG_KEYBOARD_KEYMAP="/usr/lib/kbd/keymaps/i386/qwerty/us.map"

BOARD_TARGETS := qemu
2 changes: 1 addition & 1 deletion config/busybox.config
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ CONFIG_DEFAULT_DEPMOD_FILE="modules.dep"
# CONFIG_FEATURE_ACPID_COMPAT is not set
# CONFIG_BLKDISCARD is not set
CONFIG_BLKID=y
# CONFIG_FEATURE_BLKID_TYPE is not set
CONFIG_FEATURE_BLKID_TYPE=y
CONFIG_BLOCKDEV=y
# CONFIG_CAL is not set
# CONFIG_CHRT is not set
Expand Down
18 changes: 18 additions & 0 deletions doc/boot-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,24 @@ menu, system info, power off.
When booting from an ISO file on USB media, `kexec-iso-init.sh` handles the ISO
boot flow. Invoked from Options → Boot Options → "USB boot".

When a USB drive is a dd-written hybrid ISO (an iso9660 filesystem on the
whole raw device that also carries a partition table, e.g. Kicksecure's GPT
hybrid), `mount-usb.sh` probes the whole device with an actual read-only
`mount` via `first_mountable_usb_disk()` (centralized in
`initrd/etc/functions.sh`) instead of relying on `blkid TYPE="iso9660"`
tagging. This whole-disk probe runs only when `--whole-disk` is passed;
the default is partitions-only. The USB boot path opts in via
`mount_usb --whole-disk` (the `mount_usb()` wrapper in
`initrd/etc/gui_functions.sh` forwards its arguments to `mount-usb.sh`).
If the whole device mounts, `/dev/sdX` is used directly instead of a
partition. This is required because `list_usb_storage` returns only
the device's partitions, none of which are individually mountable. The
mount is validated by parse-boot (`kexec-select-boot.sh`): if the whole
device does not mount, or yields no boot entries, `media-scan.sh` retries
with the default partitions-only picker (`mount-usb.sh` with no flag).
The boot then proceeds via the bootable-USB path (kexec-select-boot)
rather than the ISO-file path.

### Flow (execution order)

The ISO boot flow consists of 7 steps, with branching after step 3:
Expand Down
14 changes: 14 additions & 0 deletions doc/busybox_perks.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ These are listed by `busybox --list` but the bash builtin takes precedence in th
BusyBox awk supports `-F SEP` (field separator) and `-v VAR=VAL`.
Sufficient for all Heads usage: `awk '{print $2}' /proc/mounts`, `index()`.

### blkid
BusyBox `blkid` emits `TYPE="..."` only when `CONFIG_FEATURE_BLKID_TYPE=y`
is set in `config/busybox.config`. Without it, blkid prints `UUID="..."`
but no TYPE, and any code parsing `TYPE=` silently gets empty. Which
filesystem types blkid can detect is gated by the `CONFIG_FEATURE_VOLUMEID_*`
probers (e.g. `FEATURE_VOLUMEID_ISO9660=y`). `blkid DEV` prints a single
line of space-separated `KEY="value"` pairs. Use `_get_blkid_fstype()` in
`initrd/etc/functions.sh` to extract the TYPE (prints empty when blkid
reports none); it is the shared helper for all fstype lookups. It is
currently used by `initrd/bin/kexec-iso-init.sh` for ISO filesystem-type
detection. `initrd/bin/mount-usb.sh` does not use blkid TYPE: it probes
whole USB disks with an actual read-only `mount` via
`first_mountable_usb_disk()`.

### cpio
**BusyBox quirk**: Stops at first TRAILER. GNU reads past it and exits 2.
Heads pattern: `cpio -i -d "${CPIO_ARGS[@]}" 2>/dev/null || true`.
Expand Down
26 changes: 24 additions & 2 deletions doc/iso_boot.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,31 @@ partition table). The QEMU USB image creation in `targets/qemu.mk`
now creates an MBR partition table + single ext4 partition to
work around this.

**dd-written hybrid ISOs:** Kicksecure's dd'd ISO is an iso9660 filesystem
on the whole device plus a GPT partition table. Heads' `mount-usb.sh`
probes the whole device with an actual read-only `mount` (via
`first_mountable_usb_disk()` in `initrd/etc/functions.sh`) instead of
relying on blkid `TYPE="iso9660"` tagging. The probe runs only when
`--whole-disk` is passed; the default is partitions-only. The USB boot
path opts in via `mount_usb --whole-disk`. If the whole device mounts,
it is used directly, so such drives boot via the bootable-USB path.
If the whole device does not mount, or `kexec-select-boot` finds no boot
entries on it, `media-scan.sh` retries with the default partitions-only
picker (`mount-usb.sh` with no flag). Partitionless dd'd ISOs (Tails) and
MBR hybrids (Debian live) already work through the existing
partition/whole-device selection.

Note: `mount_usb()` (defined in `initrd/etc/gui_functions.sh`) is an
interactive wrapper around the `mount-usb.sh` script. It unmounts any
prior `/media` mount, maps a picker abort (`mount-usb.sh` exit 5) to
exit 1, and re-prompts the user to insert a USB drive (then retries) on
mount failure. It forwards all its arguments to `mount-usb.sh`, so the
boot path calls `mount_usb --whole-disk` to opt into whole-disk probing
while retaining the interactive prompt and abort handling.

## Test expectations

The ISO boot test (`initrd/tests/iso-test/iso-boot-test.sh`) verifies:
The ISO boot test (`tests/iso-test/iso-boot-test.sh`) verifies:

1. **Kernel display driver detection** — decompresses bzImage, searches for
built-in driver symbols (`vesadrm_probe`, `vesafb_probe`, `simpledrm_probe`).
Expand Down Expand Up @@ -251,7 +273,7 @@ kernel arguments.

## Distro Compatibility Notes

The test harness (`initrd/tests/iso-test/iso-boot-test.sh`) validates
The test harness (`tests/iso-test/iso-boot-test.sh`) validates
USB boot **detection** — kernel display symbols, initramfs filesystem
modules, isoboot keywords, and boot menu markers. It does **not**
test whether the OS installs correctly with Heads' TPM+LUKS workflow.
Expand Down
27 changes: 27 additions & 0 deletions doc/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,15 @@ DIE is for fatal errors from which Heads cannot recover. Execution stops after D

DIE always goes to debug.log and is always shown on the console regardless of output mode.

## Exit codes

Scripts terminate with a nonzero exit code on failure:

* `DIE()` prints the fatal error and exits with code 1.
* `mount-usb.sh` exits with code 5 when the user aborts the USB disk
picker; `mount_usb()` in `initrd/etc/gui_functions.sh` maps that to
exit 1 (abort) so callers see a uniform abort/failure code.

## INPUT

INPUT is a direct replacement for the `echo "prompt"; read [flags] VAR` pattern.
Expand Down Expand Up @@ -407,3 +416,21 @@ CONFIG_DEBUG_OUTPUT=y
CONFIG_ENABLE_FUNCTION_TRACING_OUTPUT=y
CONFIG_QUIET_MODE=n
```

## Console Routing

The kernel's `console=` parameter determines which device `/dev/console`
resolves to. With multiple `console=` parameters, the **last** one becomes
the primary console for `/dev/console` (see the kernel
`Documentation/admin-guide/serial-console.rst`).

Implications for Heads log output:

- Kernel `printk` output goes to ALL registered consoles.
- `STATUS` and `NOTE` write to `/dev/console` only -- they appear only on
the primary console.
- `DEBUG`, `WARN`, and `DIE` also write to `/dev/kmsg` in debug mode,
which the kernel broadcasts to all consoles -- so they appear on the
serial console even when the framebuffer is the primary console.
- whiptail dialogs render directly to `tty0` (the framebuffer console)
and never appear on the serial console.
8 changes: 0 additions & 8 deletions initrd/bin/kexec-iso-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,6 @@ else
trap _iso_cleanup EXIT
fi

# Extract filesystem type (TYPE="...") from blkid output.
# blkid emits space-separated KEY="value" pairs; this pipeline
# splits on spaces and extracts the TYPE value.
_get_blkid_fstype() {
TRACE_FUNC
blkid "$1" 2>/dev/null | tr ' ' '\n' | sed -n 's/^TYPE="\(.*\)"$/\1/p'
}

# Source guard: when _HEADS_TEST=y, only load function definitions, skip main body
# Guard placed at end-of-functions marker below; here we wrap the Step 1-2
# body that sits ahead of the function definitions.
Expand Down
84 changes: 53 additions & 31 deletions initrd/bin/media-scan.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ fi
available_partitions="$(blkid | while read line; do echo $line | awk -F ":" {'print $1'}; done )"

if [ "$1" == "usb" ]; then
# Mount the USB boot device
mount_usb || DIE "Unable to mount /media"
# Mount the USB boot device, probing whole disks first so a dd-written
# hybrid ISO (iso9660 on the whole device) is found before partitions.
mount_usb --whole-disk || DIE "Unable to mount /media"
elif $(echo $available_partitions | grep -q "$1"); then
if grep -q /media /proc/mounts; then
umount /media \
Expand All @@ -33,6 +34,8 @@ fi
# Get USB boot device
USB_BOOT_DEV=$(grep "/media" /etc/mtab | cut -f 1 -d' ')

retried_whole_disk="n"

# Check for ISO first
get_menu_option() {
if [ -x /bin/whiptail ]; then
Expand Down Expand Up @@ -77,36 +80,55 @@ get_menu_option() {
fi
}

# create ISO menu options - search recursively for ISO files
find /media -name "*.iso" -type f 2>/dev/null | sort -r > /tmp/iso_menu.txt || true
if [ `cat /tmp/iso_menu.txt | wc -l` -gt 0 ]; then
while true; do
option=""
option_index=""
option_confirm=""
while [ -z "$option" -a "$option_index" != "s" ]
do
get_menu_option
while true; do
# create ISO menu options - search recursively for ISO files
find /media -name "*.iso" -type f 2>/dev/null | sort -r > /tmp/iso_menu.txt || true
if [ `cat /tmp/iso_menu.txt | wc -l` -gt 0 ]; then
while true; do
option=""
option_index=""
option_confirm=""
while [ -z "$option" -a "$option_index" != "s" ]
do
get_menu_option
done

MOUNTED_ISO="$option"
ISO="${option:7}" # remove /media/ to get device relative path
DO_WITH_DEBUG kexec-iso-init.sh "$MOUNTED_ISO" "$ISO" "$USB_BOOT_DEV" && break
done
fi

MOUNTED_ISO="$option"
ISO="${option:7}" # remove /media/ to get device relative path
DO_WITH_DEBUG kexec-iso-init.sh "$MOUNTED_ISO" "$ISO" "$USB_BOOT_DEV" && break
done
fi

# No *.iso files on media, try ordinary bootable USB

if [ "$CONFIG_RESTRICTED_BOOT" = y ]; then
DIE "No ISO files found, bootable USB not allowed with Restricted Boot."
fi
# No *.iso files on media, try ordinary bootable USB

WARN "Could not find any ISO, trying bootable USB"
# Attempt to pull verified config from device
if [ -x /bin/whiptail ]; then
DO_WITH_DEBUG kexec-select-boot.sh -b /media -c "*.cfg" -u -g -s
else
DO_WITH_DEBUG kexec-select-boot.sh -b /media -c "*.cfg" -u -s
fi
if [ "$CONFIG_RESTRICTED_BOOT" = y ]; then
DIE "No ISO files found, bootable USB not allowed with Restricted Boot."
fi

DIE "Something failed in selecting boot"
WARN "Could not find any ISO, trying bootable USB"
# Attempt to pull verified config from device
if [ -x /bin/whiptail ]; then
if DO_WITH_DEBUG kexec-select-boot.sh -b /media -c "*.cfg" -u -g -s; then
break
fi
else
if DO_WITH_DEBUG kexec-select-boot.sh -b /media -c "*.cfg" -u -s; then
break
fi
fi
# The whole disk was mounted (e.g. a dd-written hybrid ISO) but
# yielded no boot entries. Retry once with the default
# partitions-only mount in case the bootable filesystem is on a
# partition instead.
if [ "$retried_whole_disk" = "n" ] && is_whole_disk "$USB_BOOT_DEV"; then
retried_whole_disk="y"
umount /media 2>/dev/null || true
if ! mount-usb.sh; then
WARN "No bootable filesystem found on USB media"
exit 1
fi
USB_BOOT_DEV=$(grep "/media" /etc/mtab | cut -f 1 -d' ')
continue
fi
DIE "Something failed in selecting boot"
done
Loading