mirror of https://github.com/wolfSSL/wolfBoot.git
efi: authenticate the kernel command line via a signed HDR_CMDLINE manifest TLV
parent
9d50c1a6de
commit
4a16dce4f8
|
|
@ -285,6 +285,14 @@ Provides a value to be set with a custom tag
|
|||
an application-level public key (e.g. for verifying payloads at runtime, or for key
|
||||
rotation) in the signed manifest.
|
||||
|
||||
* `--cmdline "ascii-string"`: Adds the OS command line as a signature-covered TLV using the
|
||||
wolfBoot-reserved tag `HDR_CMDLINE` (0x0034). This is a shorthand for
|
||||
`--custom-tlv-string 0x0034 "ascii-string"` (max 255 bytes). The EFI targets (`aarch64_efi`,
|
||||
`x86_64_efi`) read this authenticated command line from the verified image and pass it to the
|
||||
Linux kernel EFI stub via `LoadOptions`, so kernel arguments (e.g. `root=`, `init=`) cannot be
|
||||
altered without breaking the image signature. Example:
|
||||
`--cmdline "root=/dev/mmcblk0p2 rw rootwait console=ttyTCU0,115200"`.
|
||||
|
||||
The 65524-byte maximum is the largest TLV value the wolfBoot header parser can walk
|
||||
past when locating the fields that follow it, such as the signature.
|
||||
|
||||
|
|
@ -293,6 +301,9 @@ Provides a value to be set with a custom tag
|
|||
must be built with a matching `IMAGE_HEADER_SIZE`, or it will fail to locate the firmware
|
||||
image at boot.
|
||||
|
||||
Note: all options, including `--cmdline` and the `--custom-tlv*` options, must appear **before**
|
||||
the positional `image key version` arguments.
|
||||
|
||||
#### Three-steps signing using external provisioning tools
|
||||
|
||||
If the private key is not accessible, while it's possible to sign payloads using
|
||||
|
|
|
|||
|
|
@ -7782,6 +7782,8 @@ Staging kernel at address D630100, size: 6658016
|
|||
|
||||
You can `Ctrl-C` or login as `root` and power off qemu with `poweroff`
|
||||
|
||||
To pass an authenticated Linux kernel command line, sign the kernel with `--cmdline "..."` (before the positional `image key version` arguments; see [Signing](Signing.md)). wolfBoot reads the `HDR_CMDLINE` TLV from the verified image and applies it to the kernel EFI stub via `LoadOptions`, so it is covered by the image signature.
|
||||
|
||||
|
||||
|
||||
## NVIDIA Jetson Orin (aarch64_efi)
|
||||
|
|
@ -7844,9 +7846,10 @@ The Jetson UEFI auto-boots removable media via `\EFI\BOOT\BOOTAA64.EFI`. Place w
|
|||
```
|
||||
\EFI\BOOT\BOOTAA64.EFI <- wolfboot.efi
|
||||
\kernel.img <- signed payload (read from the volume root)
|
||||
\cmdline.txt <- optional Linux kernel command line (see below)
|
||||
```
|
||||
|
||||
The Linux kernel command line is signed into `kernel.img` itself (see "Booting Linux" below), so no separate file is placed on the ESP.
|
||||
|
||||
Insert the card and power on; UEFI auto-launches wolfBoot, which verifies and boots the payload. The debug console on the Orin Nano Developer Kit is the J14 button header (not the 40-pin), 115200 8N1. Example output:
|
||||
|
||||
```
|
||||
|
|
@ -7866,32 +7869,28 @@ An AArch64 Linux `Image` carries a PE/COFF EFI stub, so wolfBoot boots it with t
|
|||
|
||||
Obtain a Tegra234-compatible kernel and root filesystem from the [NVIDIA Jetson Linux (L4T)](https://developer.nvidia.com/embedded/jetson-linux) BSP. The driver package (`Jetson_Linux_R36.x.x_aarch64.tbz2`) contains `Linux_for_Tegra/kernel/Image` and `Linux_for_Tegra/kernel/dtb/tegra234-*.dtb`; the matching `Tegra_Linux_Sample-Root-Filesystem_*.tbz2` provides the rootfs.
|
||||
|
||||
Sign the kernel `Image` with the wolfBoot key and name it `kernel.img`:
|
||||
Sign the kernel `Image` with the wolfBoot key, including the kernel command line as an authenticated TLV (`--cmdline`, before the positional arguments), and name it `kernel.img`:
|
||||
|
||||
```
|
||||
./tools/keytools/sign --ed25519 --sha256 Linux_for_Tegra/kernel/Image \
|
||||
wolfboot_signing_private_key.der 1
|
||||
./tools/keytools/sign --ed25519 --sha256 \
|
||||
--cmdline "root=/dev/mmcblk0p2 rw rootwait console=ttyTCU0,115200" \
|
||||
Linux_for_Tegra/kernel/Image wolfboot_signing_private_key.der 1
|
||||
cp Linux_for_Tegra/kernel/Image_v1_signed.bin kernel.img
|
||||
```
|
||||
|
||||
The command line is stored in the signed manifest header (`HDR_CMDLINE` TLV) and is therefore covered by the image signature; wolfBoot reads it from the verified image and passes it to the kernel EFI stub via `LoadOptions`. For a long command line, set `IMAGE_HEADER_SIZE=512` when signing (the default 256-byte ED25519/SHA256 header holds roughly 70 command-line bytes).
|
||||
|
||||
Lay out the microSD as a FAT ESP plus a rootfs partition and place:
|
||||
|
||||
```
|
||||
FAT (p1): \EFI\BOOT\BOOTAA64.EFI <- wolfboot.efi (UEFI auto-boots this)
|
||||
\kernel.img <- signed L4T kernel
|
||||
\cmdline.txt <- kernel command line (below)
|
||||
\kernel.img <- signed L4T kernel (command line inside)
|
||||
ext4 (p2): the L4T sample root filesystem
|
||||
```
|
||||
|
||||
`cmdline.txt` (read by wolfBoot and passed to the kernel via `LoadOptions`):
|
||||
|
||||
```
|
||||
root=/dev/mmcblk0p2 rw rootwait console=ttyTCU0,115200
|
||||
```
|
||||
|
||||
On power-up the Jetson UEFI auto-boots `\EFI\BOOT\BOOTAA64.EFI`; wolfBoot verifies `kernel.img` and hands off to the kernel, which receives the real Tegra234 device tree from the UEFI configuration table (`EFI stub: Using DTB from configuration table`), mounts `mmcblk0p2`, and brings up systemd and the login on the J14 debug console (`ttyTCU0`).
|
||||
|
||||
Security note: the plaintext `\cmdline.txt` is convenient for development but is not covered by the wolfBoot image signature (it is, however, measured into the TPM via TCG2, so it remains attestable). For a production trust chain, prefer an authenticated command line. The simplest and most robust option is to bake the command line into the kernel itself: build Linux with `CONFIG_CMDLINE="..."` together with `CONFIG_CMDLINE_FORCE`, which makes the kernel ignore any command line supplied by UEFI/wolfBoot entirely -- this is independent of wolfBoot and cannot be overridden by an attacker who controls the ESP. Alternatively, supply the arguments through the `/chosen` `bootargs` of a signed device tree, or carry them in a signed wolfBoot manifest TLV (a custom TLV added at sign time via `--custom-tlv` and passed by wolfBoot as `LoadOptions`, so it is covered by the image signature -- not currently implemented). An initramfs-based flow (rather than a direct `root=` mount) would additionally need initrd support via the `LINUX_EFI_INITRD_MEDIA_GUID` LoadFile2 protocol, which this target does not currently implement.
|
||||
Security note: because the command line lives in the signed manifest, kernel arguments (e.g. `root=`, `init=`, security flags) cannot be altered without breaking the image signature -- unlike an unauthenticated file on the ESP. If the image carries no `HDR_CMDLINE` TLV, wolfBoot boots with no command line and the kernel uses its built-in `CONFIG_CMDLINE`. As additional (independent) hardening you may also build Linux with `CONFIG_CMDLINE="..."` plus `CONFIG_CMDLINE_FORCE`, which makes the kernel ignore any externally supplied command line entirely. An initramfs-based flow (rather than a direct `root=` mount) would additionally need initrd support via the `LINUX_EFI_INITRD_MEDIA_GUID` LoadFile2 protocol, which this target does not currently implement.
|
||||
|
||||
### Root of trust: enrolling wolfBoot into UEFI Secure Boot
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@
|
|||
#include <efi/efi.h>
|
||||
#include <efi/efilib.h>
|
||||
|
||||
/* Shared EFI helper: read the authenticated kernel command line (HDR_CMDLINE
|
||||
* TLV) from the verified manifest. Must follow the gnu-efi headers above. */
|
||||
#include "wolfboot_efi.h"
|
||||
|
||||
#ifdef __WOLFBOOT
|
||||
void hal_init(void)
|
||||
{
|
||||
|
|
@ -66,8 +70,9 @@ static EFI_HANDLE gImageHandle;
|
|||
EFI_PHYSICAL_ADDRESS kernel_addr;
|
||||
EFI_PHYSICAL_ADDRESS update_addr;
|
||||
|
||||
/* Optional Linux kernel command line, read from \cmdline.txt on the ESP and
|
||||
* handed to the kernel EFI stub via LoadOptions (see aarch64_efi_do_boot). */
|
||||
/* Linux kernel command line, read from the signed HDR_CMDLINE TLV of the
|
||||
* verified image and handed to the kernel EFI stub via LoadOptions (see
|
||||
* aarch64_efi_do_boot). NULL if the image carries no command line. */
|
||||
static CHAR16 *kernel_cmdline = NULL;
|
||||
static UINTN kernel_cmdline_bytes = 0;
|
||||
|
||||
|
|
@ -433,11 +438,13 @@ void RAMFUNCTION aarch64_efi_do_boot(const uint32_t *boot_addr)
|
|||
panic();
|
||||
}
|
||||
|
||||
/* Pass the kernel command line (from \cmdline.txt) to the loaded image via
|
||||
* LoadOptions for the Linux EFI stub. For a production trust chain the
|
||||
* cmdline should be authenticated (in the signed image or DT /chosen), not
|
||||
* a plaintext file. A direct root= boot needs no initrd; an initramfs flow
|
||||
* would add it via LINUX_EFI_INITRD_MEDIA_GUID (LoadFile2). */
|
||||
/* Pass the authenticated kernel command line to the loaded image via
|
||||
* LoadOptions for the Linux EFI stub. It comes from the HDR_CMDLINE TLV in
|
||||
* the (already verified) manifest, so it is covered by the image signature.
|
||||
* NULL if the image carries no command line (kernel uses CONFIG_CMDLINE).
|
||||
* A direct root= boot needs no initrd; an initramfs flow would add it via
|
||||
* LINUX_EFI_INITRD_MEDIA_GUID (LoadFile2). */
|
||||
kernel_cmdline = wolfBoot_efi_get_cmdline(manifest, &kernel_cmdline_bytes);
|
||||
if (kernel_cmdline != NULL) {
|
||||
status = uefi_call_wrapper(gSystemTable->BootServices->HandleProtocol, 3,
|
||||
kernelImageHandle, &lipGuid,
|
||||
|
|
@ -583,64 +590,6 @@ static int open_kernel_image(EFI_FILE_HANDLE vol, CHAR16 *filename,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Read an optional \cmdline.txt (ASCII) from the ESP into a widechar buffer
|
||||
* for the kernel LoadOptions. No-op (leaves kernel_cmdline NULL) if absent. */
|
||||
static void read_cmdline(EFI_FILE_HANDLE vol)
|
||||
{
|
||||
EFI_FILE_HANDLE file;
|
||||
EFI_STATUS status;
|
||||
UINT64 sz;
|
||||
UINTN readsz, i, n;
|
||||
uint8_t *ascii = NULL;
|
||||
CHAR16 *wide = NULL;
|
||||
|
||||
file = openFile(L"cmdline.txt", vol);
|
||||
if (file == NULL)
|
||||
return; /* optional */
|
||||
|
||||
sz = FileSize(file);
|
||||
if (sz == 0 || sz > 4096) {
|
||||
uefi_call_wrapper(file->Close, 1, file);
|
||||
return;
|
||||
}
|
||||
|
||||
status = uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData,
|
||||
(UINTN)sz, (void**)&ascii);
|
||||
if (status != EFI_SUCCESS || ascii == NULL) {
|
||||
uefi_call_wrapper(file->Close, 1, file);
|
||||
return;
|
||||
}
|
||||
|
||||
readsz = (UINTN)sz;
|
||||
status = uefi_call_wrapper(file->Read, 3, file, &readsz, ascii);
|
||||
uefi_call_wrapper(file->Close, 1, file); /* done with the file */
|
||||
if (status != EFI_SUCCESS) {
|
||||
FreePool(ascii);
|
||||
return;
|
||||
}
|
||||
|
||||
/* trim trailing CR/LF/whitespace */
|
||||
n = (UINTN)readsz;
|
||||
while (n > 0 && (ascii[n-1] == '\n' || ascii[n-1] == '\r' ||
|
||||
ascii[n-1] == ' ' || ascii[n-1] == '\t'))
|
||||
n--;
|
||||
|
||||
status = uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData,
|
||||
(n + 1) * sizeof(CHAR16), (void**)&wide);
|
||||
if (status != EFI_SUCCESS || wide == NULL) {
|
||||
FreePool(ascii);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < n; i++)
|
||||
wide[i] = (CHAR16)ascii[i];
|
||||
wide[n] = 0;
|
||||
FreePool(ascii);
|
||||
|
||||
kernel_cmdline = wide;
|
||||
kernel_cmdline_bytes = (n + 1) * sizeof(CHAR16);
|
||||
wolfBoot_printf("Kernel cmdline (%d chars) from cmdline.txt\n", (int)n);
|
||||
}
|
||||
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
|
||||
|
|
@ -665,7 +614,6 @@ efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
|
|||
if (status == EFI_SUCCESS)
|
||||
wolfBoot_printf("Image base: 0x%lx\n", loaded_image->ImageBase);
|
||||
vol = GetVolume(ImageHandle);
|
||||
read_cmdline(vol);
|
||||
/* open_kernel_image() leaves *_addr == 0 on failure (and frees any pages),
|
||||
* so the "no image" check below is reliable; the sizes are logged inside
|
||||
* and not needed here. */
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@
|
|||
#include <efi/efi.h>
|
||||
#include <efi/efilib.h>
|
||||
|
||||
/* Shared EFI helper: read the authenticated kernel command line (HDR_CMDLINE
|
||||
* TLV) from the verified manifest. Must follow the gnu-efi headers above. */
|
||||
#include "wolfboot_efi.h"
|
||||
|
||||
#ifdef __WOLFBOOT
|
||||
void hal_init(void)
|
||||
{
|
||||
|
|
@ -105,9 +109,17 @@ void RAMFUNCTION x86_64_efi_do_boot(uint32_t *boot_addr, uint8_t *dts_address)
|
|||
MEMMAP_DEVICE_PATH mem_path_device[2];
|
||||
EFI_HANDLE kernelImageHandle;
|
||||
EFI_STATUS status;
|
||||
CHAR16 *kernel_cmdline;
|
||||
UINTN kernel_cmdline_bytes = 0;
|
||||
EFI_LOADED_IMAGE *kernel_li = NULL;
|
||||
EFI_GUID lipGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
|
||||
|
||||
size = (uint32_t *)(manifest + 4);
|
||||
|
||||
/* Authenticated kernel command line from the verified image's HDR_CMDLINE
|
||||
* TLV (covered by the signature); NULL if the image carries none. */
|
||||
kernel_cmdline = wolfBoot_efi_get_cmdline(manifest, &kernel_cmdline_bytes);
|
||||
|
||||
mem_path_device->Header.Type = EFI_DEVICE_PATH_PROTOCOL_HW_TYPE;
|
||||
mem_path_device->Header.SubType = EFI_DEVICE_PATH_PROTOCOL_MEM_SUBTYPE;
|
||||
mem_path_device->MemoryType = EfiLoaderData;
|
||||
|
|
@ -132,6 +144,18 @@ void RAMFUNCTION x86_64_efi_do_boot(uint32_t *boot_addr, uint8_t *dts_address)
|
|||
panic();
|
||||
}
|
||||
|
||||
/* Hand the authenticated command line to the loaded image via LoadOptions
|
||||
* for the Linux EFI stub. */
|
||||
if (kernel_cmdline != NULL) {
|
||||
status = uefi_call_wrapper(gSystemTable->BootServices->HandleProtocol, 3,
|
||||
kernelImageHandle, &lipGuid,
|
||||
(void**)&kernel_li);
|
||||
if (status == EFI_SUCCESS && kernel_li != NULL) {
|
||||
kernel_li->LoadOptions = kernel_cmdline;
|
||||
kernel_li->LoadOptionsSize = (UINT32)kernel_cmdline_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
status = uefi_call_wrapper(gSystemTable->BootServices->StartImage,
|
||||
3,
|
||||
kernelImageHandle, 0, NULL);
|
||||
|
|
|
|||
|
|
@ -235,6 +235,10 @@ extern "C" {
|
|||
#define HDR_POLICY_SIGNATURE 0x21
|
||||
#define HDR_SECONDARY_SIGNATURE 0x22
|
||||
#define HDR_CERT_CHAIN 0x23
|
||||
/* OS command line (ASCII), a wolfBoot-reserved tag in the custom range
|
||||
* (0x0030-0xFEFE). Signature-covered, unlike an external cmdline file; consumed
|
||||
* by the EFI targets and passed to the kernel via LoadOptions. */
|
||||
#define HDR_CMDLINE 0x0034
|
||||
#define HDR_PADDING 0xFF
|
||||
|
||||
/* Auth Key types */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
/* wolfboot_efi.h
|
||||
*
|
||||
* Shared helpers for the wolfBoot EFI-application targets (aarch64_efi,
|
||||
* x86_64_efi).
|
||||
*
|
||||
* Copyright (C) 2026 wolfSSL Inc.
|
||||
*
|
||||
* This file is part of wolfBoot.
|
||||
*
|
||||
* wolfBoot is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wolfBoot is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
||||
*/
|
||||
|
||||
#ifndef WOLFBOOT_EFI_H
|
||||
#define WOLFBOOT_EFI_H
|
||||
|
||||
/* IMPORTANT: include this AFTER the gnu-efi headers (<efi/efi.h>,
|
||||
* <efi/efilib.h>) so the EFI types (CHAR16/UINTN/BS/uefi_call_wrapper) are
|
||||
* visible. wolfBoot declarations (HDR_CMDLINE, IMAGE_HEADER_OFFSET,
|
||||
* wolfBoot_find_header) come from wolfboot/wolfboot.h, included below. */
|
||||
#include "wolfboot/wolfboot.h"
|
||||
|
||||
/* Return the authenticated OS command line (the HDR_CMDLINE TLV) from a
|
||||
* VERIFIED wolfBoot manifest header as a NUL-terminated CHAR16 buffer suitable
|
||||
* for an EFI LoadedImage's LoadOptions. The TLV is covered by the image
|
||||
* signature, so this must be called only AFTER the image has been verified.
|
||||
*
|
||||
* `manifest` points at the wolfBoot header (i.e. boot_addr - IMAGE_HEADER_SIZE).
|
||||
* Returns NULL and sets *out_bytes = 0 when the image carries no command-line
|
||||
* TLV (the caller then boots with no command line, so the kernel uses its
|
||||
* built-in CONFIG_CMDLINE). The returned buffer is allocated with
|
||||
* BS->AllocatePool (EfiLoaderData) and includes the terminating NUL in
|
||||
* *out_bytes (a byte count). */
|
||||
static CHAR16 *wolfBoot_efi_get_cmdline(const uint8_t *manifest, UINTN *out_bytes)
|
||||
{
|
||||
uint8_t *val = NULL;
|
||||
uint16_t len;
|
||||
UINTN i;
|
||||
CHAR16 *wide = NULL;
|
||||
EFI_STATUS status;
|
||||
|
||||
*out_bytes = 0;
|
||||
if (manifest == NULL)
|
||||
return NULL;
|
||||
|
||||
len = wolfBoot_find_header((uint8_t *)(manifest + IMAGE_HEADER_OFFSET),
|
||||
HDR_CMDLINE, &val);
|
||||
if (len == 0 || val == NULL)
|
||||
return NULL; /* no signed command line present */
|
||||
|
||||
status = uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData,
|
||||
((UINTN)len + 1) * sizeof(CHAR16), (void **)&wide);
|
||||
if (status != EFI_SUCCESS || wide == NULL)
|
||||
return NULL;
|
||||
|
||||
/* The TLV value is ASCII and not NUL-terminated; widen exactly len bytes. */
|
||||
for (i = 0; i < (UINTN)len; i++)
|
||||
wide[i] = (CHAR16)val[i];
|
||||
wide[len] = 0;
|
||||
|
||||
*out_bytes = ((UINTN)len + 1) * sizeof(CHAR16);
|
||||
return wide;
|
||||
}
|
||||
|
||||
#endif /* WOLFBOOT_EFI_H */
|
||||
|
|
@ -3572,6 +3572,38 @@ int main(int argc, char** argv)
|
|||
CMD.custom_tlvs++;
|
||||
i += 2;
|
||||
}
|
||||
else if (strcmp(argv[i], "--cmdline") == 0) {
|
||||
/* Shorthand for --custom-tlv-string HDR_CMDLINE "<args>": stores the
|
||||
* OS command line as a signature-covered TLV in the manifest. */
|
||||
int p = CMD.custom_tlvs;
|
||||
uint16_t len;
|
||||
uint32_t j;
|
||||
if (p >= MAX_CUSTOM_TLVS) {
|
||||
fprintf(stderr, "Too many custom TLVs.\n");
|
||||
exit(16);
|
||||
}
|
||||
if (argc < (i + 2)) {
|
||||
fprintf(stderr, "Missing --cmdline argument.\n");
|
||||
exit(16);
|
||||
}
|
||||
len = (uint16_t)strlen(argv[i + 1]);
|
||||
if (len == 0 || len > 255) {
|
||||
fprintf(stderr, "cmdline must be 1..255 bytes: %s\n", argv[i + 1]);
|
||||
exit(16);
|
||||
}
|
||||
CMD.custom_tlv[p].tag = HDR_CMDLINE;
|
||||
CMD.custom_tlv[p].len = len;
|
||||
CMD.custom_tlv[p].buffer = malloc(len);
|
||||
if (CMD.custom_tlv[p].buffer == NULL) {
|
||||
fprintf(stderr, "Error malloc for cmdline tlv buffer %d\n", len);
|
||||
exit(16);
|
||||
}
|
||||
for (j = 0; j < len; j++) {
|
||||
CMD.custom_tlv[p].buffer[j] = (uint8_t)argv[i + 1][j];
|
||||
}
|
||||
CMD.custom_tlvs++;
|
||||
i += 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--cert-chain") == 0) {
|
||||
if (argc <= (i + 1)) {
|
||||
fprintf(stderr, "Missing certificate chain file argument\n");
|
||||
|
|
|
|||
Loading…
Reference in New Issue