This commit is contained in:
2026-05-26 03:27:31 -07:00
commit 7c08b986d4
11 changed files with 1121 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
output/
build/
iso_root/
keys/
input/*.efi
input/autoexec.ipxe
input/boot.ipxe
input/shim/*

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "includes/deps/gnu-efi"]
path = includes/deps/gnu-efi
url = https://github.com/rhboot/gnu-efi.git

294
Makefile Normal file
View File

@@ -0,0 +1,294 @@
PROJECT_NAME = ipxe-stub
SRC_DIR = src
BUILD_DIR = build
GNU_EFI_DIR = includes/deps/gnu-efi
OUTPUT_DIR = output
KEYS_DIR = keys
SHIM_DIR = input/shim
OPENSSL_CNF = openssl.cnf
SIGNING_KEY = $(KEYS_DIR)/signing.key
SIGNING_CERT = $(KEYS_DIR)/signing.crt
SIGNING_DER = $(KEYS_DIR)/signing.der
HOST_ARCH := $(shell uname -m)
ARCH ?= $(HOST_ARCH)
ifeq ($(ARCH),x86_64)
TARGET_ARCH = x86_64
EFI_BINARY = BOOTX64.EFI
STUB_BINARY = stubx64.efi
ENROLL_BINARY = enroll-x64.efi
ARCH_CFLAGS = -mno-red-zone
else ifeq ($(ARCH),aarch64)
TARGET_ARCH = aarch64
EFI_BINARY = BOOTAA64.EFI
STUB_BINARY = stubaa64.efi
ENROLL_BINARY = enroll-aa64.efi
ARCH_CFLAGS =
else ifeq ($(ARCH),ia32)
TARGET_ARCH = ia32
EFI_BINARY = BOOTIA32.EFI
STUB_BINARY = stubia32.efi
ENROLL_BINARY = enroll-ia32.efi
ARCH_CFLAGS =
else
$(error Unsupported ARCH=$(ARCH). Use x86_64, aarch64, or ia32)
endif
ifeq ($(HOST_ARCH),x86_64)
HOST_ARCH_NORM = x86_64
else ifeq ($(HOST_ARCH),aarch64)
HOST_ARCH_NORM = aarch64
else ifeq ($(HOST_ARCH),i686)
HOST_ARCH_NORM = ia32
else
HOST_ARCH_NORM = unknown
endif
ifneq ($(HOST_ARCH_NORM),$(TARGET_ARCH))
CROSS = 1
else
CROSS = 0
endif
ifeq ($(CROSS),1)
ifeq ($(TARGET_ARCH),x86_64)
CROSS_PREFIX = x86_64-linux-gnu-
else ifeq ($(TARGET_ARCH),aarch64)
CROSS_PREFIX = aarch64-linux-gnu-
else ifeq ($(TARGET_ARCH),ia32)
CROSS_PREFIX = i686-linux-gnu-
endif
CC = $(CROSS_PREFIX)gcc
LD = $(CROSS_PREFIX)ld
AR = $(CROSS_PREFIX)ar
OBJCOPY = $(CROSS_PREFIX)objcopy
else
CC = gcc
LD = ld
AR = ar
OBJCOPY = objcopy
endif
GNU_EFI_INC = $(GNU_EFI_DIR)/inc
GNU_EFI_LIB_DIR = $(GNU_EFI_DIR)/$(TARGET_ARCH)/lib
GNU_EFI_GNUEFI = $(GNU_EFI_DIR)/gnuefi
ARCH_BUILD_DIR = $(BUILD_DIR)/$(TARGET_ARCH)
EFI_BUILD_DIR = $(ARCH_BUILD_DIR)/efi
EFI_CRT0 = $(EFI_BUILD_DIR)/crt0-efi-$(TARGET_ARCH).o
EFI_RELOC = $(EFI_BUILD_DIR)/reloc_$(TARGET_ARCH).o
EFI_LIBEFI = $(GNU_EFI_LIB_DIR)/libefi.a
EFI_LIBGNUEFI = $(EFI_BUILD_DIR)/libgnuefi.a
EFI_LDS = $(GNU_EFI_GNUEFI)/elf_$(TARGET_ARCH)_efi.lds
CFLAGS = -I$(GNU_EFI_INC) \
-I$(GNU_EFI_INC)/$(TARGET_ARCH) \
-I$(GNU_EFI_INC)/protocol \
-Iincludes \
-fno-stack-protector \
-fpic \
-fshort-wchar \
$(ARCH_CFLAGS) \
-DEFI_FUNCTION_WRAPPER
LDFLAGS = -nostdlib \
-znocombreloc \
-T $(EFI_LDS) \
-shared \
-Bsymbolic \
-L$(GNU_EFI_LIB_DIR) \
-L$(EFI_BUILD_DIR) \
$(EFI_CRT0)
LIBS = -l:libgnuefi.a -l:libefi.a
OBJCOPY_FLAGS = -j .text \
-j .sdata \
-j .data \
-j .rodata \
-j .dynamic \
-j .dynsym \
-j .rel \
-j .rela \
-j .reloc \
--target=efi-app-$(TARGET_ARCH)
LOADER_OBJ = $(ARCH_BUILD_DIR)/loader.o
LOADER_SO = $(ARCH_BUILD_DIR)/loader.so
TARGET_EFI = $(OUTPUT_DIR)/$(EFI_BINARY)
STUB_OBJ = $(ARCH_BUILD_DIR)/stub.o
STUB_SO = $(ARCH_BUILD_DIR)/stub.so
TARGET_STUB = $(OUTPUT_DIR)/$(STUB_BINARY)
ENROLL_OBJ = $(ARCH_BUILD_DIR)/enroll.o
ENROLL_SO = $(ARCH_BUILD_DIR)/enroll.so
TARGET_ENROLL = $(OUTPUT_DIR)/$(ENROLL_BINARY)
.PHONY: all enroll clean distclean all-arches check-deps test-efi info keygen sign iso
all: check-deps $(TARGET_EFI) $(TARGET_STUB) $(TARGET_ENROLL)
@echo "✓ Built: $(TARGET_EFI) (loader → $(STUB_BINARY))"
@echo "✓ Built: $(TARGET_STUB)"
@echo "✓ Built: $(TARGET_ENROLL) (→ mm$(TARGET_ARCH).efi on ISO)"
all-arches:
$(MAKE) all ARCH=x86_64
$(MAKE) all ARCH=aarch64
$(MAKE) all ARCH=ia32
enroll: check-deps $(TARGET_ENROLL)
@echo "✓ Built: $(TARGET_ENROLL)"
check-deps:
@echo "Host: $(HOST_ARCH) Target: $(TARGET_ARCH) Mode: $(if $(filter 1,$(CROSS)),cross,native)"
ifeq ($(CROSS),1)
@which $(CC) > /dev/null 2>&1 || \
(echo "Install: sudo apt install gcc-$(TARGET_ARCH)-linux-gnu binutils-$(TARGET_ARCH)-linux-gnu" && exit 1)
endif
$(EFI_LIBEFI):
@echo "→ Building libefi.a"
@if [ "$(CROSS)" = "1" ]; then \
$(MAKE) -C $(GNU_EFI_DIR) ARCH=$(TARGET_ARCH) CROSS_COMPILE=$(CROSS_PREFIX) lib; \
else \
$(MAKE) -C $(GNU_EFI_DIR) ARCH=$(TARGET_ARCH) lib; \
fi
$(EFI_CRT0): | $(EFI_BUILD_DIR)
$(CC) -I$(GNU_EFI_INC) -I$(GNU_EFI_INC)/$(TARGET_ARCH) \
-fno-stack-protector -fpic \
-c -o $@ $(GNU_EFI_GNUEFI)/crt0-efi-$(TARGET_ARCH).S
$(EFI_RELOC): | $(EFI_BUILD_DIR)
$(CC) -I$(GNU_EFI_INC) -I$(GNU_EFI_INC)/$(TARGET_ARCH) \
-fno-stack-protector -fpic -fshort-wchar $(ARCH_CFLAGS) \
-c -o $@ $(GNU_EFI_GNUEFI)/reloc_$(TARGET_ARCH).c
$(EFI_LIBGNUEFI): $(EFI_RELOC) | $(EFI_BUILD_DIR)
$(AR) rcs $@ $(EFI_RELOC)
$(BUILD_DIR) $(ARCH_BUILD_DIR) $(OUTPUT_DIR) $(EFI_BUILD_DIR):
@mkdir -p $@
$(LOADER_OBJ): src/loader.c | $(ARCH_BUILD_DIR)
$(CC) $(CFLAGS) -DSTUB_NAME='"$(STUB_BINARY)"' -c $< -o $@
$(LOADER_SO): $(LOADER_OBJ) $(EFI_CRT0) $(EFI_LIBGNUEFI) $(EFI_LIBEFI)
$(LD) $(LDFLAGS) $(LOADER_OBJ) -o $@ $(LIBS)
$(TARGET_EFI): $(LOADER_SO) | $(OUTPUT_DIR)
$(OBJCOPY) $(OBJCOPY_FLAGS) $(LOADER_SO) $@
$(STUB_OBJ): src/stub.c | $(ARCH_BUILD_DIR)
$(CC) $(CFLAGS) -c $< -o $@
$(STUB_SO): $(STUB_OBJ) $(EFI_CRT0) $(EFI_LIBGNUEFI) $(EFI_LIBEFI)
$(LD) $(LDFLAGS) $(STUB_OBJ) -o $@ $(LIBS)
$(TARGET_STUB): $(STUB_SO) | $(OUTPUT_DIR)
$(OBJCOPY) $(OBJCOPY_FLAGS) $(STUB_SO) $@
$(ENROLL_OBJ): src/enroll.c | $(ARCH_BUILD_DIR)
$(CC) $(CFLAGS) -c $< -o $@
$(ENROLL_SO): $(ENROLL_OBJ) $(EFI_CRT0) $(EFI_LIBGNUEFI) $(EFI_LIBEFI)
$(LD) $(LDFLAGS) $(ENROLL_OBJ) -o $@ $(LIBS)
$(TARGET_ENROLL): $(ENROLL_SO) | $(OUTPUT_DIR)
$(OBJCOPY) $(OBJCOPY_FLAGS) $(ENROLL_SO) $@
test-efi:
@if [ -f "$(TARGET_EFI)" ]; then \
echo -n " Type: "; file $(TARGET_EFI) | grep -oE "PE32\+? executable.*EFI" || echo "unknown"; \
echo -n " Size: "; ls -lh $(TARGET_EFI) | awk '{print $$5}'; \
fi
keygen:
@if [ -f $(SIGNING_KEY) ]; then \
echo "Keys already exist. Delete keys/ to regenerate."; exit 1; \
fi
bash gen-key.sh
@echo " openssl.cnf — config used for key generation"
@echo "✓ Keys generated in $(KEYS_DIR)/"
@echo " signing.key — keep private"
@echo " signing.crt — PEM cert"
@echo " signing.der — DER cert (place at EFI/BOOT/cert.der on ISO)"
sign: all-arches
@if [ ! -f $(SIGNING_KEY) ]; then \
echo "No signing key found. Run: make keygen"; exit 1; \
fi
@which sbsign > /dev/null 2>&1 || \
(echo "Install: sudo apt install sbsigntool" && exit 1)
@for efi in $(OUTPUT_DIR)/BOOT*.EFI $(OUTPUT_DIR)/stub*.efi; do \
[ -f "$$efi" ] || continue; \
sbsign --key $(SIGNING_KEY) --cert $(SIGNING_CERT) \
--output "$$efi" "$$efi" 2>&1 | grep -v "^warning:"; \
echo "✓ Signed: $$efi"; \
done
@[ -f $(OUTPUT_DIR)/enroll-x64.efi ] && \
sbsign --key $(SIGNING_KEY) --cert $(SIGNING_CERT) \
--output $(OUTPUT_DIR)/mmx64.efi $(OUTPUT_DIR)/enroll-x64.efi 2>&1 | grep -v "^warning:" && \
echo "✓ Signed: $(OUTPUT_DIR)/mmx64.efi" || true
@[ -f $(OUTPUT_DIR)/enroll-aa64.efi ] && \
sbsign --key $(SIGNING_KEY) --cert $(SIGNING_CERT) \
--output $(OUTPUT_DIR)/mmaa64.efi $(OUTPUT_DIR)/enroll-aa64.efi 2>&1 | grep -v "^warning:" && \
echo "✓ Signed: $(OUTPUT_DIR)/mmaa64.efi" || true
@[ -f $(OUTPUT_DIR)/enroll-ia32.efi ] && \
sbsign --key $(SIGNING_KEY) --cert $(SIGNING_CERT) \
--output $(OUTPUT_DIR)/mmia32.efi $(OUTPUT_DIR)/enroll-ia32.efi 2>&1 | grep -v "^warning:" && \
echo "✓ Signed: $(OUTPUT_DIR)/mmia32.efi" || true
iso: sign
@which xorriso > /dev/null 2>&1 || \
(echo "Install: sudo apt install xorriso" && exit 1)
@if [ ! -f $(SIGNING_DER) ]; then \
echo "No signing.der found. Run: make keygen"; exit 1; \
fi
@rm -rf iso_root
@mkdir -p iso_root/EFI/BOOT iso_root/IPXE
@# Our signed stubs — firmware picks the right arch automatically
cp $(OUTPUT_DIR)/BOOTX64.EFI iso_root/EFI/BOOT/BOOTX64.EFI
@[ -f $(OUTPUT_DIR)/BOOTAA64.EFI ] && \
cp $(OUTPUT_DIR)/BOOTAA64.EFI iso_root/EFI/BOOT/BOOTAA64.EFI || true
@[ -f $(OUTPUT_DIR)/BOOTIA32.EFI ] && \
cp $(OUTPUT_DIR)/BOOTIA32.EFI iso_root/EFI/BOOT/BOOTIA32.EFI || true
@# stub.efi — arch-neutral, loaded by all BOOT*.EFI stubs
cp $(OUTPUT_DIR)/stub.efi iso_root/EFI/BOOT/stub.efi
@# Pre-signed + renamed MOK manager binaries
@[ -f $(OUTPUT_DIR)/mmx64.efi ] && \
cp $(OUTPUT_DIR)/mmx64.efi iso_root/EFI/BOOT/mmx64.efi || true
@[ -f $(OUTPUT_DIR)/mmaa64.efi ] && \
cp $(OUTPUT_DIR)/mmaa64.efi iso_root/EFI/BOOT/mmaa64.efi || true
@[ -f $(OUTPUT_DIR)/mmia32.efi ] && \
cp $(OUTPUT_DIR)/mmia32.efi iso_root/EFI/BOOT/mmia32.efi || true
@# Signing cert — enroll.efi reads this; also enrollable via UEFI setup
cp $(SIGNING_DER) iso_root/EFI/BOOT/cert.der
@# iPXE binaries
@if [ -f ipxe/ipxe.efi ]; then cp ipxe/ipxe.efi iso_root/IPXE/ipxe.efi; \
else echo "Warning: ipxe/ipxe.efi not found — IPXE/ will be empty"; fi
@if [ -f ipxe/ipxe-snp.efi ]; then cp ipxe/ipxe-snp.efi iso_root/IPXE/ipxe-snp.efi; \
else echo "Warning: ipxe/ipxe-snp.efi not found"; fi
xorriso -as mkisofs \
-o output/ipxe-boot.iso \
-e EFI/BOOT/BOOTX64.EFI \
-no-emul-boot \
-isohybrid-gpt-basdat \
iso_root
@echo "✓ ISO: output/ipxe-boot.iso"
clean:
rm -rf $(BUILD_DIR) $(OUTPUT_DIR) iso_root
distclean: clean
$(MAKE) -C $(GNU_EFI_DIR) clean > /dev/null 2>&1 || true
info:
@echo "Target: $(TARGET_ARCH) Binary: $(EFI_BINARY) Compiler: $(CC)"

110
gen-efi-media.sh Executable file
View File

@@ -0,0 +1,110 @@
#!/usr/bin/env bash
set -euo pipefail
IN="input"
OUT="output"
ISO_ROOT="iso_root"
IMG="$OUT/efiboot.img"
ISO="$OUT/ipxe.iso"
rm -rf "$ISO_ROOT" "$IMG" "$ISO"
mkdir -p "$ISO_ROOT/EFI/BOOT" "$ISO_ROOT/IPXE" "$OUT"
# stub.efi for direct loading by BOOT*.EFI and future direct loading by firmware (and future shim flow)
cp "$OUT/stubx64.efi" "$ISO_ROOT/EFI/BOOT/stubx64.efi"
[ -f "$OUT/stubaa64.efi" ] && cp "$OUT/stubaa64.efi" "$ISO_ROOT/EFI/BOOT/"
[ -f "$OUT/stubia32.efi" ] && cp "$OUT/stubia32.efi" "$ISO_ROOT/EFI/BOOT/"
# Current flow: direct EFI boot
cp "$OUT/BOOTX64.EFI" "$ISO_ROOT/EFI/BOOT/BOOTX64.EFI"
[ -f "$OUT/BOOTAA64.EFI" ] && cp "$OUT/BOOTAA64.EFI" "$ISO_ROOT/EFI/BOOT/"
[ -f "$OUT/BOOTIA32.EFI" ] && cp "$OUT/BOOTIA32.EFI" "$ISO_ROOT/EFI/BOOT/"
# Future flow: shim → grub*.efi/BOOT*.EFI → stub*.efi
# cp "input/shim/shimx64.efi" "$ISO_ROOT/EFI/BOOT/BOOTX64.EFI"
# cp "$OUT/BOOTX64.EFI" "$ISO_ROOT/EFI/BOOT/grubx64.efi"
# MOK manager binaries for direct loading by shim and future direct loading by firmware (and future shim flow)
[ -f "$OUT/mmx64.efi" ] && cp "$OUT/mmx64.efi" "$ISO_ROOT/EFI/BOOT/"
[ -f "$OUT/mmaa64.efi" ] && cp "$OUT/mmaa64.efi" "$ISO_ROOT/EFI/BOOT/"
[ -f "$OUT/mmia32.efi" ] && cp "$OUT/mmia32.efi" "$ISO_ROOT/EFI/BOOT/"
# Signing cert for direct loading by shim and future direct loading by firmware (and future shim flow)
cp keys/signing.der "$ISO_ROOT/EFI/BOOT/cert.der"
# iPXE binaries for direct loading by firmware and future shim flow
[ -f "$IN/ipxe-snp.efi" ] && cp "$IN/ipxe-snp.efi" "$ISO_ROOT/IPXE/ipxe-snp.efi"
[ -f "$IN/ipxe.efi" ] && cp "$IN/ipxe.efi" "$ISO_ROOT/IPXE/ipxe.efi"
# If autoexec.ipxe file is present, copy it over to the ISO root
[ -f "$IN/autoexec.ipxe" ] && cp "$IN/autoexec.ipxe" "$ISO_ROOT/autoexec.ipxe"
# Build EFI boot image
dd if=/dev/zero of="$IMG" bs=1M count=8
mkfs.vfat -F 12 "$IMG"
mmd -i "$IMG" ::/EFI
mmd -i "$IMG" ::/EFI/BOOT
# Copy BOOT*.EFI to the EFI System Partition for direct loading by shim (and future direct loading by firmware)
mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/BOOTX64.EFI" ::/EFI/BOOT/BOOTX64.EFI
# Copy stub*.efi to the EFI System Partition
mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/stubx64.efi" ::/EFI/BOOT/stubx64.efi
[ -f "$ISO_ROOT/EFI/BOOT/stubaa64.efi" ] && \
mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/stubaa64.efi" ::/EFI/BOOT/stubaa64.efi
[ -f "$ISO_ROOT/EFI/BOOT/stubia32.efi" ] && \
mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/stubia32.efi" ::/EFI/BOOT/stubia32.efi
# Copy BOOT*.EFI, cert.der, and MOK manager binaries to the EFI System Partition for direct loading by firmware (and future shim flow)
[ -f "$ISO_ROOT/EFI/BOOT/BOOTAA64.EFI" ] && \
mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/BOOTAA64.EFI" ::/EFI/BOOT/BOOTAA64.EFI
[ -f "$ISO_ROOT/EFI/BOOT/BOOTIA32.EFI" ] && \
mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/BOOTIA32.EFI" ::/EFI/BOOT/BOOTIA32.EFI
[ -f "$ISO_ROOT/EFI/BOOT/cert.der" ] && \
mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/cert.der" ::/EFI/BOOT/cert.der
[ -f "$ISO_ROOT/EFI/BOOT/mmx64.efi" ] && \
mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/mmx64.efi" ::/EFI/BOOT/mmx64.efi
[ -f "$ISO_ROOT/EFI/BOOT/mmaa64.efi" ] && \
mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/mmaa64.efi" ::/EFI/BOOT/mmaa64.efi
[ -f "$ISO_ROOT/EFI/BOOT/mmia32.efi" ] && \
mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/mmia32.efi" ::/EFI/BOOT/mmia32.efi
# Copy iPXE binaries to the EFI System Partition for direct loading by firmware (and future shim flow)
mmd -i "$IMG" ::/IPXE
[ -f "$ISO_ROOT/IPXE/ipxe-snp.efi" ] && \
mcopy -i "$IMG" "$ISO_ROOT/IPXE/ipxe-snp.efi" ::/IPXE/ipxe-snp.efi
[ -f "$ISO_ROOT/IPXE/ipxe.efi" ] && \
mcopy -i "$IMG" "$ISO_ROOT/IPXE/ipxe.efi" ::/IPXE/ipxe.efi
# If autoexec.ipxe file is present, copy it over to the EFI System Partition
[ -f "$ISO_ROOT/IPXE/autoexec.ipxe" ] && \
mcopy -i "$IMG" "$ISO_ROOT/autoexec.ipxe" ::/IPXE/autoexec.ipxe
# build EFI System Partition image for El Torito booting
cp "$IMG" "$ISO_ROOT/efiboot.img"
# Build ISO
xorriso -as mkisofs \
-iso-level 3 \
-full-iso9660-filenames \
-volid "IPXE_BOOT" \
-eltorito-platform efi \
-eltorito-boot efiboot.img \
-no-emul-boot \
-append_partition 2 0xef "$IMG" \
-appended_part_as_gpt \
-output "$ISO" \
"$ISO_ROOT"
xorriso -indev "$ISO" -report_el_torito plain
echo "Created $ISO"

14
gen-key.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
mkdir -p keys
openssl req -newkey rsa:2048 -nodes \
-keyout keys/signing.key \
-new -x509 -days 3650 \
-config openssl.cnf \
-subj "/CN=iPXE Boot Signing Key" \
-out keys/signing.crt
# DER format — this is the cert.der enroll.efi reads
openssl x509 -in keys/signing.crt -outform DER -out keys/signing.der

1
includes/deps/gnu-efi Submodule

Submodule includes/deps/gnu-efi added at 74bd9b60ba

43
includes/mok.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef MOK_H
#define MOK_H
#include <efi.h>
/* MOK variables GUID */
#define MOK_GUID \
{ 0x605dab50, 0xe046, 0x4300, \
{0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23} }
/* EFI X.509 certificate type GUID (used in EFI_SIGNATURE_LIST) */
#define EFI_CERT_X509_GUID \
{ 0xa5c059a1, 0x94e4, 0x4aa7, \
{0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72} }
/*
* Owner GUID embedded in EFI_SIGNATURE_DATA when enrolling.
* Spells "NETIPXEBOOT" in ASCII — arbitrary but stable identifier.
*/
#define MOK_OWNER_GUID \
{ 0x4e455449, 0x504f, 0x5854, \
{0x49, 0x50, 0x58, 0x45, 0x42, 0x4f, 0x4f, 0x54} }
#define MOK_ATTRS \
(EFI_VARIABLE_NON_VOLATILE | \
EFI_VARIABLE_BOOTSERVICE_ACCESS | \
EFI_VARIABLE_RUNTIME_ACCESS)
#pragma pack(1)
typedef struct {
EFI_GUID SignatureType;
UINT32 SignatureListSize;
UINT32 SignatureHeaderSize;
UINT32 SignatureSize;
} MOK_SIG_LIST;
typedef struct {
EFI_GUID SignatureOwner;
/* followed by variable-length cert/data bytes */
} MOK_SIG_DATA;
#pragma pack()
#endif /* MOK_H */

27
openssl.cnf Normal file
View File

@@ -0,0 +1,27 @@
HOME = keys
RANDFILE = keys/.rnd
[ req ]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
string_mask = utf8only
[ req_distinguished_name ]
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:FALSE
# We use extended key usage information to limit what this auto-generated
# key can be used for.
#
# codeSigning: specifies that this key is used to sign code.
#
# 1.3.6.1.4.1.2312.16.1.2: defines this key as used for module signing
# only. See https://lkml.org/lkml/2015/8/26/741.
#
extendedKeyUsage = codeSigning,1.3.6.1.4.1.2312.16.1.2
nsComment = "OpenSSL Generated Certificate"

473
src/enroll.c Normal file
View File

@@ -0,0 +1,473 @@
#include <efi.h>
#include <efilib.h>
#include "mok.h"
/* ── Minimal freestanding SHA-256 ───────────────────────────────────────── */
#define SHA256_BLOCK 64
#define SHA256_DIGEST 32
typedef struct {
UINT32 state[8];
UINT64 count;
UINT8 buf[SHA256_BLOCK];
} SHA256_CTX;
static const UINT32 sha256_k[64] = {
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,
0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,
0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,
0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,
0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,
0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,
0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,
0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,
0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};
#define ROR32(v,n) (((v) >> (n)) | ((v) << (32-(n))))
#define CH(x,y,z) (((x)&(y))^(~(x)&(z)))
#define MAJ(x,y,z) (((x)&(y))^((x)&(z))^((y)&(z)))
#define S0(x) (ROR32(x,2) ^ROR32(x,13)^ROR32(x,22))
#define S1(x) (ROR32(x,6) ^ROR32(x,11)^ROR32(x,25))
#define G0(x) (ROR32(x,7) ^ROR32(x,18)^((x)>>3))
#define G1(x) (ROR32(x,17)^ROR32(x,19)^((x)>>10))
static void sha256_transform(SHA256_CTX *ctx, const UINT8 *data) {
UINT32 a,b,c,d,e,f,g,h,t1,t2,w[64];
UINTN i;
for (i = 0; i < 16; i++)
w[i] = ((UINT32)data[i*4]<<24)|((UINT32)data[i*4+1]<<16)|
((UINT32)data[i*4+2]<<8)|(UINT32)data[i*4+3];
for (i = 16; i < 64; i++)
w[i] = G1(w[i-2])+w[i-7]+G0(w[i-15])+w[i-16];
a=ctx->state[0]; b=ctx->state[1]; c=ctx->state[2]; d=ctx->state[3];
e=ctx->state[4]; f=ctx->state[5]; g=ctx->state[6]; h=ctx->state[7];
for (i = 0; i < 64; i++) {
t1 = h + S1(e) + CH(e,f,g) + sha256_k[i] + w[i];
t2 = S0(a) + MAJ(a,b,c);
h=g; g=f; f=e; e=d+t1;
d=c; c=b; b=a; a=t1+t2;
}
ctx->state[0]+=a; ctx->state[1]+=b; ctx->state[2]+=c; ctx->state[3]+=d;
ctx->state[4]+=e; ctx->state[5]+=f; ctx->state[6]+=g; ctx->state[7]+=h;
}
static void sha256_init(SHA256_CTX *ctx) {
ctx->count = 0;
ctx->state[0] = 0x6a09e667; ctx->state[1] = 0xbb67ae85;
ctx->state[2] = 0x3c6ef372; ctx->state[3] = 0xa54ff53a;
ctx->state[4] = 0x510e527f; ctx->state[5] = 0x9b05688c;
ctx->state[6] = 0x1f83d9ab; ctx->state[7] = 0x5be0cd19;
}
static void sha256_update(SHA256_CTX *ctx, const UINT8 *data, UINTN len) {
UINTN used = (UINTN)(ctx->count & 63);
ctx->count += len;
if (used) {
UINTN free = SHA256_BLOCK - used;
if (len < free) { CopyMem(ctx->buf + used, (void *)data, len); return; }
CopyMem(ctx->buf + used, (void *)data, free);
sha256_transform(ctx, ctx->buf);
data += free; len -= free;
}
while (len >= SHA256_BLOCK) {
sha256_transform(ctx, data);
data += SHA256_BLOCK; len -= SHA256_BLOCK;
}
CopyMem(ctx->buf, (void *)data, len);
}
static void sha256_final(SHA256_CTX *ctx, UINT8 *digest) {
UINT64 bits = ctx->count * 8;
UINTN used = (UINTN)(ctx->count & 63);
UINTN i;
ctx->buf[used++] = 0x80;
if (used > 56) {
SetMem(ctx->buf + used, SHA256_BLOCK - used, 0);
sha256_transform(ctx, ctx->buf);
used = 0;
}
SetMem(ctx->buf + used, 56 - used, 0);
for (i = 0; i < 8; i++)
ctx->buf[56+i] = (UINT8)(bits >> (56 - i*8));
sha256_transform(ctx, ctx->buf);
for (i = 0; i < 8; i++) {
digest[i*4] = (UINT8)(ctx->state[i] >> 24);
digest[i*4+1] = (UINT8)(ctx->state[i] >> 16);
digest[i*4+2] = (UINT8)(ctx->state[i] >> 8);
digest[i*4+3] = (UINT8)(ctx->state[i]);
}
}
static void sha256(const UINT8 *data, UINTN len, UINT8 *digest) {
SHA256_CTX ctx;
sha256_init(&ctx);
sha256_update(&ctx, data, len);
sha256_final(&ctx, digest);
}
/* ── UI helpers ─────────────────────────────────────────────────────────── */
static void print_line(UINTN width) {
UINTN i;
for (i = 0; i < width; i++) Print(L"");
Print(L"\n");
}
static void print_hex_line(const UINT8 *d, UINTN offset, UINTN count) {
UINTN i;
for (i = 0; i < count; i++) {
if (i) Print(L":");
Print(L"%02x", d[offset + i]);
}
}
static void print_fingerprint(const UINT8 *fp) {
Print(L" Fingerprint (SHA-256):\n");
Print(L" "); print_hex_line(fp, 0, 16); Print(L"\n");
Print(L" "); print_hex_line(fp, 16, 16); Print(L"\n");
}
/* Read a masked password. Returns length, buffer is NOT null-terminated. */
static UINTN read_password(UINT8 *buf, UINTN max) {
UINTN len = 0;
EFI_INPUT_KEY key;
while (1) {
uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
if (key.UnicodeChar == 0) continue;
if (key.UnicodeChar == CHAR_CARRIAGE_RETURN) break;
if (key.UnicodeChar == CHAR_BACKSPACE) {
if (len) { len--; Print(L"\b \b"); }
continue;
}
if (len < max) {
buf[len++] = (UINT8)key.UnicodeChar;
Print(L"*");
}
}
Print(L"\n");
return len;
}
/* Wait for one of the listed unicode chars, return which one. */
static CHAR16 wait_for_key(const CHAR16 *allowed) {
EFI_INPUT_KEY key;
while (1) {
uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
if (!key.UnicodeChar) continue;
const CHAR16 *p = allowed;
while (*p) { if (*p == key.UnicodeChar) return *p; p++; }
}
}
/* ── File reading ───────────────────────────────────────────────────────── */
static EFI_STATUS read_file(EFI_HANDLE image, CHAR16 *path,
UINT8 **out, UINTN *out_len) {
EFI_STATUS status;
EFI_LOADED_IMAGE *loaded = NULL;
EFI_FILE_IO_INTERFACE *fs = NULL;
EFI_FILE_HANDLE root = NULL, file = NULL;
EFI_FILE_INFO *info = NULL;
UINTN info_size;
UINT8 *buf = NULL;
UINTN size;
EFI_GUID fs_guid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
EFI_GUID info_guid = EFI_FILE_INFO_ID;
status = uefi_call_wrapper(BS->HandleProtocol, 3,
image, &LoadedImageProtocol, (void **)&loaded);
if (EFI_ERROR(status)) return status;
status = uefi_call_wrapper(BS->HandleProtocol, 3,
loaded->DeviceHandle, &fs_guid, (void **)&fs);
if (EFI_ERROR(status)) return status;
status = uefi_call_wrapper(fs->OpenVolume, 2, fs, &root);
if (EFI_ERROR(status)) return status;
status = uefi_call_wrapper(root->Open, 5,
root, &file, path, EFI_FILE_MODE_READ, 0);
if (EFI_ERROR(status)) { root->Close(root); return status; }
info_size = sizeof(EFI_FILE_INFO) + 256;
status = uefi_call_wrapper(BS->AllocatePool, 3,
EfiLoaderData, info_size, (void **)&info);
if (EFI_ERROR(status)) goto out;
status = uefi_call_wrapper(file->GetInfo, 4,
file, &info_guid, &info_size, info);
if (EFI_ERROR(status)) goto out;
size = (UINTN)info->FileSize;
status = uefi_call_wrapper(BS->AllocatePool, 3,
EfiLoaderData, size, (void **)&buf);
if (EFI_ERROR(status)) goto out;
status = uefi_call_wrapper(file->Read, 3, file, &size, buf);
if (EFI_ERROR(status)) {
uefi_call_wrapper(BS->FreePool, 1, buf);
goto out;
}
*out = buf;
*out_len = size;
out:
if (info) uefi_call_wrapper(BS->FreePool, 1, info);
uefi_call_wrapper(file->Close, 1, file);
uefi_call_wrapper(root->Close, 1, root);
return status;
}
/* ── MOK variable helpers ───────────────────────────────────────────────── */
static EFI_GUID mok_guid = MOK_GUID;
static EFI_GUID efi_cert_x509_guid = EFI_CERT_X509_GUID;
static EFI_GUID owner_guid = MOK_OWNER_GUID;
static EFI_STATUS write_mok_new(const UINT8 *cert, UINTN cert_len) {
UINTN list_size = sizeof(MOK_SIG_LIST) +
sizeof(MOK_SIG_DATA) + cert_len;
UINT8 *list;
EFI_STATUS status = uefi_call_wrapper(BS->AllocatePool, 3,
EfiLoaderData, list_size, (void **)&list);
if (EFI_ERROR(status)) return status;
MOK_SIG_LIST *hdr = (MOK_SIG_LIST *)list;
CopyMem(&hdr->SignatureType, (void *)&efi_cert_x509_guid, sizeof(EFI_GUID));
hdr->SignatureListSize = (UINT32)list_size;
hdr->SignatureHeaderSize = 0;
hdr->SignatureSize = (UINT32)(sizeof(MOK_SIG_DATA) + cert_len);
MOK_SIG_DATA *sig = (MOK_SIG_DATA *)(list + sizeof(MOK_SIG_LIST));
CopyMem(&sig->SignatureOwner, (void *)&owner_guid, sizeof(EFI_GUID));
CopyMem((UINT8 *)sig + sizeof(MOK_SIG_DATA), (void *)cert, cert_len);
status = uefi_call_wrapper(RT->SetVariable, 5,
L"MokNew", &mok_guid, MOK_ATTRS, list_size, list);
uefi_call_wrapper(BS->FreePool, 1, list);
return status;
}
static EFI_STATUS write_mok_auth(const UINT8 *hash) {
return uefi_call_wrapper(RT->SetVariable, 5,
L"MokAuth", &mok_guid, MOK_ATTRS,
SHA256_DIGEST, (void *)hash);
}
static BOOLEAN mok_new_exists(void) {
UINTN size = 0;
UINT32 attrs = 0;
EFI_STATUS s = uefi_call_wrapper(RT->GetVariable, 5,
L"MokNew", &mok_guid, &attrs, &size, NULL);
return (s == EFI_BUFFER_TOO_SMALL);
}
static EFI_STATUS delete_mok_auth(void) {
return uefi_call_wrapper(RT->SetVariable, 5,
L"MokAuth", &mok_guid, MOK_ATTRS, 0, NULL);
}
/* ── Phase 1: initial enrollment request ───────────────────────────────── */
static EFI_STATUS phase_enroll(EFI_HANDLE image) {
EFI_STATUS status;
UINT8 *cert = NULL;
UINTN cert_len = 0;
UINT8 fp[SHA256_DIGEST];
UINT8 pw1[128], pw2[128];
UINTN pw1_len, pw2_len;
UINT8 pw_hash[SHA256_DIGEST];
UINTN i;
status = read_file(image, L"\\EFI\\BOOT\\cert.der", &cert, &cert_len);
if (EFI_ERROR(status)) {
Print(L" Error: cert.der not found (\\EFI\\BOOT\\cert.der): %r\n", status);
return status;
}
sha256(cert, cert_len, fp);
uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
print_line(60);
Print(L" iPXE Boot - Secure Boot Key Enrollment\n");
print_line(60);
Print(L"\n");
Print(L" Certificate: \\EFI\\BOOT\\cert.der (%d bytes)\n", cert_len);
Print(L"\n");
print_fingerprint(fp);
Print(L"\n");
Print(L" This key will be enrolled as a Machine Owner Key (MOK).\n");
Print(L" Binaries signed with it will be trusted under Secure Boot.\n");
Print(L"\n");
Print(L" Press ENTER to continue, ESC to cancel.\n");
Print(L"\n");
if (wait_for_key(L"\r\x1b") == L'\x1b') {
Print(L" Cancelled.\n");
uefi_call_wrapper(BS->FreePool, 1, cert);
return EFI_ABORTED;
}
/* Password */
while (1) {
Print(L" Enter password (remember this for next boot): ");
pw1_len = read_password(pw1, sizeof(pw1));
Print(L" Confirm password: ");
pw2_len = read_password(pw2, sizeof(pw2));
if (pw1_len != pw2_len) {
Print(L" Passwords do not match. Try again.\n\n");
continue;
}
BOOLEAN match = TRUE;
for (i = 0; i < pw1_len; i++) {
if (pw1[i] != pw2[i]) { match = FALSE; break; }
}
if (!match) {
Print(L" Passwords do not match. Try again.\n\n");
continue;
}
break;
}
sha256(pw1, pw1_len, pw_hash);
Print(L"\n Writing MokNew... ");
status = write_mok_new(cert, cert_len);
uefi_call_wrapper(BS->FreePool, 1, cert);
if (EFI_ERROR(status)) {
Print(L"failed: %r\n", status);
return status;
}
Print(L"OK\n");
Print(L" Writing MokAuth... ");
status = write_mok_auth(pw_hash);
if (EFI_ERROR(status)) {
Print(L"failed: %r\n", status);
return status;
}
Print(L"OK\n");
Print(L"\n");
print_line(60);
Print(L" Enrollment queued. On next boot, shim will launch this\n");
Print(L" app again to confirm. Enter the same password to complete.\n");
print_line(60);
Print(L"\n");
Print(L" Press R to reboot, ESC to exit.\n");
if (wait_for_key(L"rR\x1b") != L'\x1b') {
uefi_call_wrapper(RT->ResetSystem, 4,
EfiResetWarm, EFI_SUCCESS, 0, NULL);
}
return EFI_SUCCESS;
}
/* ── Phase 2: confirmation (launched by shim as mmx64.efi) ─────────────── */
static EFI_STATUS phase_confirm(EFI_HANDLE image) {
UINT8 stored_hash[SHA256_DIGEST];
UINTN stored_size = SHA256_DIGEST;
UINT32 attrs = 0;
UINT8 pw[128];
UINTN pw_len;
UINT8 pw_hash[SHA256_DIGEST];
UINTN i;
/* Read stored MokAuth */
EFI_STATUS status = uefi_call_wrapper(RT->GetVariable, 5,
L"MokAuth", &mok_guid, &attrs, &stored_size, stored_hash);
if (EFI_ERROR(status) || stored_size != SHA256_DIGEST) {
Print(L" Error: MokAuth not found or wrong size.\n");
return EFI_NOT_FOUND;
}
uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
print_line(60);
Print(L" iPXE Boot - Confirm MOK Enrollment\n");
print_line(60);
Print(L"\n");
Print(L" A Machine Owner Key is pending enrollment.\n");
Print(L" Enter the password you set during enrollment to confirm.\n");
Print(L"\n");
/* Allow 3 attempts */
for (i = 0; i < 3; i++) {
Print(L" Password: ");
pw_len = read_password(pw, sizeof(pw));
sha256(pw, pw_len, pw_hash);
BOOLEAN match = TRUE;
UINTN j;
for (j = 0; j < SHA256_DIGEST; j++) {
if (pw_hash[j] != stored_hash[j]) { match = FALSE; break; }
}
if (match) {
delete_mok_auth();
Print(L"\n Password verified. MOK will be enrolled.\n");
Print(L"\n");
print_line(60);
Print(L" Shim will now finalize enrollment and reboot.\n");
print_line(60);
Print(L"\n Press any key...\n");
EFI_INPUT_KEY key;
while (uefi_call_wrapper(ST->ConIn->ReadKeyStroke,
2, ST->ConIn, &key) == EFI_NOT_READY) {}
return EFI_SUCCESS;
}
Print(L" Incorrect password. %d attempt(s) remaining.\n\n",
(int)(2 - i));
}
Print(L" Too many failed attempts. Enrollment cancelled.\n");
/* Delete MokNew + MokAuth to cancel */
uefi_call_wrapper(RT->SetVariable, 5,
L"MokNew", &mok_guid, MOK_ATTRS, 0, NULL);
delete_mok_auth();
return EFI_ACCESS_DENIED;
}
/* ── Entry point ────────────────────────────────────────────────────────── */
EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
InitializeLib(ImageHandle, SystemTable);
uefi_call_wrapper(ST->BootServices->SetWatchdogTimer, 4, 0, 0, 0, NULL);
/* If MokNew already exists we're in phase 2 (shim called us to confirm) */
if (mok_new_exists()) {
EFI_STATUS s = phase_confirm(ImageHandle);
if (EFI_ERROR(s)) {
uefi_call_wrapper(BS->Stall, 1, 3000000);
}
return s;
}
return phase_enroll(ImageHandle);
}

46
src/loader.c Normal file
View File

@@ -0,0 +1,46 @@
#include <efi.h>
#include <efilib.h>
/*
* STUB_NAME is defined by the Makefile per-arch (e.g. "stubx64.efi").
* When shim is introduced, BOOT{ARCH}.EFI is replaced by shim and this
* file is retired — shim loads stub{arch}.efi directly.
*/
#ifndef STUB_NAME
#define STUB_NAME "stubx64.efi" /* default to x64 */
#endif
#define WIDEN_(x) L##x
#define WIDEN(x) WIDEN_(x)
#define STUB_PATH (L"\\EFI\\BOOT\\" WIDEN(STUB_NAME))
static EFI_STATUS chainload(EFI_HANDLE ImageHandle, CHAR16 *path) {
EFI_STATUS status;
EFI_HANDLE child = NULL;
EFI_LOADED_IMAGE *loaded = NULL;
EFI_DEVICE_PATH *devpath = NULL;
status = uefi_call_wrapper(BS->HandleProtocol, 3,
ImageHandle, &LoadedImageProtocol, (void **)&loaded);
if (EFI_ERROR(status)) return status;
devpath = FileDevicePath(loaded->DeviceHandle, path);
if (!devpath) return EFI_OUT_OF_RESOURCES;
status = uefi_call_wrapper(BS->LoadImage, 6,
FALSE, ImageHandle, devpath, NULL, 0, &child);
if (EFI_ERROR(status)) {
Print(L"loader: failed to load %s (%r)\n", path, status);
return status;
}
status = uefi_call_wrapper(BS->StartImage, 3, child, NULL, NULL);
uefi_call_wrapper(BS->UnloadImage, 1, child);
return status;
}
EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
InitializeLib(ImageHandle, SystemTable);
return chainload(ImageHandle, STUB_PATH);
}

102
src/stub.c Normal file
View File

@@ -0,0 +1,102 @@
#include <efi.h>
#include <efilib.h>
#define IPXE_PATH L"\\IPXE\\ipxe.efi"
#define IPXE_SNP_PATH L"\\IPXE\\ipxe-snp.efi"
/* ── SMBIOS ─────────────────────────────────────────────────────────────── */
static BOOLEAN ascii_equals(const CHAR8 *a, const CHAR16 *b) {
if (!a) return FALSE;
while (*b) {
if ((CHAR16)(UINT8)*a != *b) return FALSE;
a++; b++;
}
return *a == 0;
}
static BOOLEAN is_apple(void) {
void *table = NULL;
UINT8 *structs = NULL;
UINT32 len = 0;
if (!EFI_ERROR(LibGetSystemConfigurationTable(&SMBIOS3TableGuid, &table)) && table) {
SMBIOS3_STRUCTURE_TABLE *e = (SMBIOS3_STRUCTURE_TABLE *)table;
structs = (UINT8 *)(UINTN)e->TableAddress;
len = e->TableMaximumSize;
} else if (!EFI_ERROR(LibGetSystemConfigurationTable(&SMBIOSTableGuid, &table)) && table) {
SMBIOS_STRUCTURE_TABLE *e = (SMBIOS_STRUCTURE_TABLE *)table;
structs = (UINT8 *)(UINTN)e->TableAddress;
len = e->TableLength;
} else {
return FALSE;
}
UINT8 *p = structs;
UINT8 *end = structs + len;
while (p + sizeof(SMBIOS_HEADER) <= end) {
SMBIOS_STRUCTURE_POINTER sp;
sp.Raw = p;
if (sp.Hdr->Type == 127) break;
if (sp.Hdr->Type == 0 && sp.Hdr->Length >= sizeof(SMBIOS_TYPE0)) {
CHAR8 *vendor = LibGetSmbiosString(&sp, sp.Type0->Vendor);
return ascii_equals(vendor, L"Apple Inc.");
}
UINT8 *s = p + sp.Hdr->Length;
while (s + 1 < end) {
if (s[0] == 0 && s[1] == 0) { s += 2; break; }
s++;
}
p = s;
}
return FALSE;
}
/* ── Chainloader ────────────────────────────────────────────────────────── */
static EFI_STATUS chainload(EFI_HANDLE ImageHandle, CHAR16 *path) {
EFI_STATUS status;
EFI_HANDLE child = NULL;
EFI_LOADED_IMAGE *loaded = NULL;
EFI_DEVICE_PATH *devpath = NULL;
status = uefi_call_wrapper(BS->HandleProtocol, 3,
ImageHandle, &LoadedImageProtocol, (void **)&loaded);
if (EFI_ERROR(status)) return status;
devpath = FileDevicePath(loaded->DeviceHandle, path);
if (!devpath) return EFI_OUT_OF_RESOURCES;
status = uefi_call_wrapper(BS->LoadImage, 6,
FALSE, ImageHandle, devpath, NULL, 0, &child);
if (EFI_ERROR(status)) {
Print(L"stub: failed to load %s (%r)\n", path, status);
return status;
}
status = uefi_call_wrapper(BS->StartImage, 3, child, NULL, NULL);
uefi_call_wrapper(BS->UnloadImage, 1, child);
return status;
}
/* ── Entry point ────────────────────────────────────────────────────────── */
EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
InitializeLib(ImageHandle, SystemTable);
CHAR16 *target = is_apple() ? IPXE_SNP_PATH : IPXE_PATH;
EFI_STATUS status = chainload(ImageHandle, target);
if (EFI_ERROR(status)) {
Print(L"stub: chainload failed (%r) — halting\n", status);
uefi_call_wrapper(BS->Stall, 1, 5000000);
}
return status;
}