Bug 254637

Summary: [PATCH] Read kern.geom.eli.passphrase from UEFI variable for unattended boot without passphrase on disk
Product: Base System Reporter: Kyle George <kgeorge>
Component: kernAssignee: freebsd-geom (Nobody) <geom>
Status: New ---    
Severity: Affects Many People CC: colin, jlduran, jo, ruben
Priority: --- Keywords: patch
Version: 12.2-RELEASE   
Hardware: Any   
OS: Any   
Attachments:
Description Flags
Patch to loader.efi to read kern.geom.eli.passphrase from UEFI environment none

Description Kyle George 2021-03-29 06:50:34 UTC
Created attachment 223677 [details]
Patch to loader.efi to read kern.geom.eli.passphrase from UEFI environment

TL;DR this patch reads kern.geom.eli.passphrase from a namespaced UEFI "environment variable" (NVRAM firmware variable), setting it in the boot environment for unattended boot of encrypted boot disks, without storing anything on-disk in the clear.

GELI is used in FreeBSD to encrypt disks / partitions.  When doing encryption of the boot disk, the options to decrypt the boot disk at boot time are either enter a passphrase, or (if applicable) keep an unencrypted /boot partition on the disk with keys.  However, in addition to not being a great idea (consider the threat model of having to RMA a drive, for example), the latter option does not work with UEFI: the UEFI loader.efi [0] stage probes devices and when it probes a GELI partition, it prompts for a passphrase.  So storing on disk in /boot is not an option with UEFI (and not really great anyway), and, at least on ZFS root, /boot is part of the zpool and thus encrypted by GELI regardless.  While the /boot option does work with legacy BIOS in some configurations, this is going away on modern systems.

The FreeBSD boot system already looks for the variable kern.geom.eli.passphrase in the boot runtime environment and, when present, this passphrase is used before prompting the user when it encounters a GELI-encrypted partition.  So if there was some way to get kern.geom.eli.passphrase populated it would solve this problem ...

UEFI has the ability to store arbitrary, namespaced variables in system NVRAM.  These are stored separate from any boot media (like on the motherboard).  This is good for at least two reasons: 1) we can read them early in the boot process, separate from any disk block and 2) because they're separate from the disks, the keys are never written in the clear to disk.  FreeBSD already has a (mostly unused, as far as I can tell) UEFI variable namespace (cfee69ad-a0de-47a9-93a8-f63106f8ae99).  We can store kern.geom.eli.passphrase there and read it into the boot environment _before_ disks are probed, making the rest of the boot infrastructure try it before prompting.  The attached patch does just that.

kern.geom.eli.passphrase can be written and read by userspace tool efivar:

# write

% cat passphrase | head -1 | tr -d $'\n' | doas efivar --name cfee69ad-a0de-47a9-93a8-f63106f8ae99-kern.geom.eli.passphrase -w

# read

% doas efivar -n cfee69ad-a0de-47a9-93a8-f63106f8ae99-kern.geom.eli.passphrase
cfee69ad-a0de-47a9-93a8-f63106f8ae99-kern.geom.eli.passphrase
0000: 74 65 73 74 31 32 33 34

      ^^^^^^^^^^^^^^^^^^^^^^^ test1234

efivar requires root to read/write.

The patch, which is fairly simple (but took a while to figure out exactly where/how to patch), is inline and attached:

    diff --git a/stand/efi/loader/main.c b/stand/efi/loader/main.c
    index a5213a51d88b..ecce7a2e4bba 100644
    --- a/stand/efi/loader/main.c
    +++ b/stand/efi/loader/main.c
    @@ -869,6 +869,8 @@ main(int argc, CHAR16 *argv[])
            char boot_info[4096];
            char buf[32];
            bool uefi_boot_mgr;
    +       char geom_eli_passphrase[256];
    +       UINTN geom_eli_bufsz;

            archsw.arch_autoload = efi_autoload;
            archsw.arch_getdev = efi_getdev;
    @@ -902,6 +904,22 @@ main(int argc, CHAR16 *argv[])
             */
            bcache_init(32768, 512);

    +       /*
    +        * Read kern.geom.eli.passphrase from the EFI environment under the
    +        * FreeBSD EFI GUID namespace (efi_freebsd_getenv).  Read before scanning
    +        * block IO media so that it's available when probing.
    +        */
    +       geom_eli_bufsz = sizeof(geom_eli_passphrase);
    +       bzero(geom_eli_passphrase, geom_eli_bufsz);
    +       rv = efi_freebsd_getenv("kern.geom.eli.passphrase", geom_eli_passphrase,
    +           &geom_eli_bufsz);
    +       if (rv == EFI_SUCCESS) {
    +               printf("kern.geom.eli.phassphrase read from EFI env\n");
    +               env_setenv("kern.geom.eli.passphrase", EV_VOLATILE,
    +                   &geom_eli_passphrase, env_noset, env_nounset);
    +               bzero(geom_eli_passphrase, geom_eli_bufsz);
    +       }
    +
            /*
             * Scan the BLOCK IO MEDIA handles then
             * march through the device switch probing for things.

To build:

    % cd /usr/src
    % patch -p1 < /path/to/patch.patch
    % cd /usr/src/stand/efi/loader
    % make
    % make install # changes /boot/loader.efi

To install:

    % mount -t msdosfs /dev/gpt/efiboot0 /mnt/efi
    % cp /boot/loader.efi /mnt/efi/efi/boot/BOOTx64.efi

To test:

    On any UEFI + GELI boot system, set the passphrase like above, and reboot.  Your system should boot without prompting (assuming your passphrase is correct, of course).

I've tested this / I'm using this on my own machine: 12.2-RELEASE amd64, with UEFI + GELI + zfsboot.  Patch is made against 12.2 branch from the git mirror.

[0] loader.efi is the first and last stage loader now, according to http://freebsd.1045724.x6.nabble.com/UEFI-loader-efi-and-boot-config-td6308485.html.  boot1.efi, which used to be the first stage, is being phased out.
Comment 1 ruben 2021-07-21 12:43:52 UTC
Tried the patch on VMWare and a Clevo NL5xRU notebook with the geli password as a efi var.
Besides VMWare UEFI being fickly works as intended. 

Though this is not UEFI secure boot it is a convenient way for server systems to have both full disk encryption and unattended reboots. 

I feel it is at the administrators discretion to determine wether to have a key/passphrase in unprotected nvram is different than on a unprotected boot partition

It would address the need of people who installed their zfs systems using a separate boot pool using preconfigured keys and want to consolidate that into a single pool so bectl/beadm starts to work for them.
Comment 2 Graham Perrin freebsd_committer freebsd_triage 2022-10-17 12:34:00 UTC
Keyword: 

    patch
or  patch-ready

– in lieu of summary line prefix: 

    [patch]

* bulk change for the keyword
* summary lines may be edited manually (not in bulk). 

Keyword descriptions and search interface: 

    <https://bugs.freebsd.org/bugzilla/describekeywords.cgi>