sd-boot: added shim signature/MOK validation (#5702)
authorMax Resch <mxre@users.noreply.github.com>
Tue, 9 May 2017 18:57:40 +0000 (20:57 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 9 May 2017 18:57:40 +0000 (20:57 +0200)
Adds support for booting in a SecureBoot environment with shim as a
preloader. Install an appropriate UEFI security policy to check PE
signature of a chained kernel or UEFI application (using LoadImage())
against the MOK database maintained by shim, using shim's installed
BootServices.

Implementation details for installing the security policy are based on
code from the LinuxFoundation's SecureBoot PreLoader, part of efitools
licensed under LGPL 2.1

Current signed (by Microsoft) versions of shim (Versions 0.8 & 0.9)
so not install a security policy by themselves, future Versions of
shim might (a compile time switch exists in rectent git versions),
so in the future this PR might become unnecessary.

Makefile.am
src/boot/efi/boot.c
src/boot/efi/meson.build
src/boot/efi/shim.c [new file with mode: 0644]
src/boot/efi/shim.h [new file with mode: 0644]

index 7f5e1c3..1284d14 100644 (file)
@@ -3005,7 +3005,8 @@ efi_headers = \
        src/boot/efi/measure.h \
        src/boot/efi/pefile.h \
        src/boot/efi/splash.h \
-       src/boot/efi/util.h
+       src/boot/efi/util.h \
+       src/boot/efi/shim.h
 
 systemd_boot_sources = \
        src/boot/efi/boot.c \
@@ -3014,7 +3015,8 @@ systemd_boot_sources = \
        src/boot/efi/graphics.c \
        src/boot/efi/measure.c \
        src/boot/efi/pefile.c \
-       src/boot/efi/util.c
+       src/boot/efi/util.c \
+       src/boot/efi/shim.c
 
 EXTRA_DIST += $(systemd_boot_sources) $(systemd_boot_headers)
 
index f5b3934..d429a12 100644 (file)
@@ -23,6 +23,7 @@
 #include "pefile.h"
 #include "util.h"
 #include "measure.h"
+#include "shim.h"
 
 #ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI
 #define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL
@@ -383,6 +384,9 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
                 FreePool(b);
         }
 
+        if (shim_loaded())
+                Print(L"Shim:                   present\n");
+
         if (efivar_get_raw(&global_guid, L"OsIndicationsSupported", &b, &size) == EFI_SUCCESS) {
                 Print(L"OsIndicationsSupported: %d\n", (UINT64)*b);
                 FreePool(b);
@@ -1745,6 +1749,14 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
                 return EFI_LOAD_ERROR;
         }
 
+        if (secure_boot_enabled() && shim_loaded()) {
+                err = security_policy_install();
+                if (EFI_ERROR(err)) {
+                        Print(L"Error installing security policy: %r ", err);
+                        uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
+                        return err;
+                }
+        }
 
         /* the filesystem path to this image, to prevent adding ourselves to the menu */
         loaded_image_path = DevicePathToStr(loaded_image->FilePath);
index 6d1b38a..9e06e22 100644 (file)
@@ -7,6 +7,7 @@ efi_headers = files('''
         pefile.h
         splash.h
         util.h
+        shim.h
 '''.split())
 
 common_sources = '''
@@ -20,6 +21,7 @@ common_sources = '''
 systemd_boot_sources = '''
         boot.c
         console.c
+        shim.c
 '''.split()
 
 stub_sources = '''
diff --git a/src/boot/efi/shim.c b/src/boot/efi/shim.c
new file mode 100644 (file)
index 0000000..0f73be9
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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
+ * Lesser General Public License for more details.
+ *
+ * Port to systemd-boot
+ * Copyright 2017 Max Resch <resch.max@gmail.com>
+ *
+ * Security Policy Handling
+ * Copyright 2012 <James.Bottomley@HansenPartnership.com>
+ * https://github.com/mjg59/efitools
+ */
+
+#include <efi.h>
+#include <efilib.h>
+
+#include "util.h"
+#include "shim.h"
+
+/* well known shim lock guid */
+#define SHIM_LOCK_GUID
+
+struct ShimLock {
+        EFI_STATUS __attribute__((sysv_abi)) (*shim_verify) (VOID *buffer, UINT32 size);
+
+        /* context is actually a struct for the PE header, but it isn't needed so void is sufficient just do define the interface
+         * see shim.c/shim.h and PeHeader.h in the github shim repo */
+        EFI_STATUS __attribute__((sysv_abi)) (*generate_hash) (VOID *data, UINT32 datasize, VOID *context, UINT8 *sha256hash, UINT8 *sha1hash);
+
+        EFI_STATUS __attribute__((sysv_abi)) (*read_header) (VOID *data, UINT32 datasize, VOID *context);
+};
+
+static const EFI_GUID simple_fs_guid = SIMPLE_FILE_SYSTEM_PROTOCOL;
+static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE;
+
+static const EFI_GUID security_protocol_guid = { 0xa46423e3, 0x4617, 0x49f1, {0xb9, 0xff, 0xd1, 0xbf, 0xa9, 0x11, 0x58, 0x39 } };
+static const EFI_GUID security2_protocol_guid = { 0x94ab2f58, 0x1438, 0x4ef1, {0x91, 0x52, 0x18, 0x94, 0x1a, 0x3a, 0x0e, 0x68 } };
+static const EFI_GUID shim_lock_guid = { 0x605dab50, 0xe046, 0x4300, {0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23} };
+
+BOOLEAN shim_loaded(void) {
+        struct ShimLock *shim_lock;
+
+        return uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &shim_lock_guid, NULL, (VOID**) &shim_lock) == EFI_SUCCESS;
+}
+
+static BOOLEAN shim_validate(VOID *data, UINT32 size) {
+        struct ShimLock *shim_lock;
+
+        if (!data)
+                return FALSE;
+
+        if (uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &shim_lock_guid, NULL, (VOID**) &shim_lock) != EFI_SUCCESS)
+                return FALSE;
+
+        if (!shim_lock)
+                return FALSE;
+
+        if (shim_lock->shim_verify(data, size) == EFI_SUCCESS)
+                return TRUE;
+
+        return FALSE;
+}
+
+BOOLEAN secure_boot_enabled(void) {
+        CHAR8 *b;
+        UINTN size;
+        BOOLEAN result;
+
+        if (efivar_get_raw(&global_guid, L"SecureBoot", &b, &size) == EFI_SUCCESS) {
+                result = *b > 0;
+                FreePool(b);
+                return result;
+        }
+
+        return FALSE;
+}
+
+/*
+ * See the UEFI Platform Initialization manual (Vol2: DXE) for this
+ */
+struct _EFI_SECURITY2_PROTOCOL;
+struct _EFI_SECURITY_PROTOCOL;
+struct _EFI_DEVICE_PATH_PROTOCOL;
+
+typedef struct _EFI_SECURITY2_PROTOCOL EFI_SECURITY2_PROTOCOL;
+typedef struct _EFI_SECURITY_PROTOCOL EFI_SECURITY_PROTOCOL;
+typedef struct _EFI_DEVICE_PATH_PROTOCOL EFI_DEVICE_PATH_PROTOCOL;
+
+typedef EFI_STATUS (EFIAPI *EFI_SECURITY_FILE_AUTHENTICATION_STATE) (
+        const EFI_SECURITY_PROTOCOL *This,
+        UINT32 AuthenticationStatus,
+        const EFI_DEVICE_PATH_PROTOCOL *File
+);
+
+typedef EFI_STATUS (EFIAPI *EFI_SECURITY2_FILE_AUTHENTICATION) (
+        const EFI_SECURITY2_PROTOCOL *This,
+        const EFI_DEVICE_PATH_PROTOCOL *DevicePath,
+        VOID *FileBuffer,
+        UINTN FileSize,
+        BOOLEAN  BootPolicy
+);
+
+struct _EFI_SECURITY2_PROTOCOL {
+        EFI_SECURITY2_FILE_AUTHENTICATION FileAuthentication;
+};
+
+struct _EFI_SECURITY_PROTOCOL {
+        EFI_SECURITY_FILE_AUTHENTICATION_STATE  FileAuthenticationState;
+};
+
+/* Handle to the original authenticator for security1 protocol */
+static EFI_SECURITY_FILE_AUTHENTICATION_STATE esfas = NULL;
+
+/* Handle to the original authenticator for security2 protocol */
+static EFI_SECURITY2_FILE_AUTHENTICATION es2fa = NULL;
+
+/*
+ * Perform shim/MOK and Secure Boot authentication on a binary that's already been
+ * loaded into memory. This function does the platform SB authentication first
+ * but preserves its return value in case of its failure, so that it can be
+ * returned in case of a shim/MOK authentication failure. This is done because
+ * the SB failure code seems to vary from one implementation to another, and I
+ * don't want to interfere with that at this time.
+ */
+static EFIAPI EFI_STATUS security2_policy_authentication (const EFI_SECURITY2_PROTOCOL *this,
+                                                          const EFI_DEVICE_PATH_PROTOCOL *device_path,
+                                                          VOID *file_buffer, UINTN file_size, BOOLEAN boot_policy) {
+        EFI_STATUS status;
+
+        /* Chain original security policy */
+        status = uefi_call_wrapper(es2fa, 5, this, device_path, file_buffer, file_size, boot_policy);
+
+        /* if OK, don't bother with MOK check */
+        if (status == EFI_SUCCESS)
+                return status;
+
+        if (shim_validate(file_buffer, file_size))
+                return EFI_SUCCESS;
+
+        return status;
+}
+
+/*
+ * Perform both shim/MOK and platform Secure Boot authentication. This function loads
+ * the file and performs shim/MOK authentication first simply to avoid double loads
+ * of Linux kernels, which are much more likely to be shim/MOK-signed than platform-signed,
+ * since kernels are big and can take several seconds to load on some computers and
+ * filesystems. This also has the effect of returning whatever the platform code is for
+ * authentication failure, be it EFI_ACCESS_DENIED, EFI_SECURITY_VIOLATION, or something
+ * else. (This seems to vary between implementations.)
+ */
+static EFIAPI EFI_STATUS security_policy_authentication (const EFI_SECURITY_PROTOCOL *this, UINT32 authentication_status,
+                                                         const EFI_DEVICE_PATH_PROTOCOL *device_path_const) {
+        EFI_STATUS status;
+        EFI_DEVICE_PATH *dev_path;
+        EFI_HANDLE h;
+        EFI_FILE *root;
+        VOID *file_buffer = NULL;
+        UINTN file_size;
+        CHAR16 *dev_path_str;
+
+        if (!device_path_const)
+                return EFI_INVALID_PARAMETER;
+
+        dev_path = DuplicateDevicePath((EFI_DEVICE_PATH*) device_path_const);
+
+        status = uefi_call_wrapper(BS->LocateDevicePath, 3, (EFI_GUID*) &simple_fs_guid, &dev_path, &h);
+        if (status != EFI_SUCCESS) {
+                FreePool(dev_path);
+                return status;
+        }
+
+        /* No need to check return value, this already happend in efi_main() */
+        root = LibOpenRoot(h);
+        dev_path_str = DevicePathToStr(dev_path);
+        FreePool(dev_path);
+
+        file_size = file_read(root, dev_path_str, 0, 0, file_buffer);
+        FreePool(dev_path_str);
+        uefi_call_wrapper(root->Close, 1, root);
+
+        if (shim_validate(file_buffer, file_size))
+                status = EFI_SUCCESS;
+
+        FreePool(file_buffer);
+
+        /* Try using the platform's native policy.... */
+        if (status != EFI_SUCCESS)
+                status = uefi_call_wrapper(esfas, 3, this, authentication_status, device_path_const);
+
+        return status;
+}
+
+EFI_STATUS security_policy_install(void) {
+        EFI_SECURITY_PROTOCOL *security_protocol;
+        EFI_SECURITY2_PROTOCOL *security2_protocol = NULL;
+        EFI_STATUS status;
+
+        /* Already Installed */
+        if (esfas)
+                return EFI_ALREADY_STARTED;
+
+        /*
+         * Don't bother with status here.  The call is allowed
+         * to fail, since SECURITY2 was introduced in PI 1.2.1
+         * If it fails, use security2_protocol == NULL as indicator
+         */
+        uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &security2_protocol_guid, NULL, (VOID**) &security2_protocol);
+
+        status = uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &security_protocol_guid, NULL, (VOID**) &security_protocol);
+         /* This one is mandatory, so there's a serious problem */
+        if (status != EFI_SUCCESS)
+                return status;
+
+        if (!security2_protocol) {
+                es2fa = security2_protocol->FileAuthentication;
+                security2_protocol->FileAuthentication = security2_policy_authentication;
+        }
+
+        esfas = security_protocol->FileAuthenticationState;
+        security_protocol->FileAuthenticationState = security_policy_authentication;
+
+        return EFI_SUCCESS;
+}
+
+EFI_STATUS security_policy_uninstall(void) {
+        EFI_STATUS status;
+
+        if (esfas) {
+                EFI_SECURITY_PROTOCOL *security_protocol;
+
+                status = uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &security_protocol_guid, NULL, (VOID**) &security_protocol);
+
+                if (status != EFI_SUCCESS)
+                        return status;
+
+                security_protocol->FileAuthenticationState = esfas;
+                esfas = NULL;
+        } else
+                /* nothing installed */
+                return EFI_NOT_STARTED;
+
+        if (es2fa) {
+                EFI_SECURITY2_PROTOCOL *security2_protocol;
+
+                status = uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &security2_protocol_guid, NULL, (VOID**) &security2_protocol);
+
+                if (status != EFI_SUCCESS)
+                        return status;
+
+                security2_protocol->FileAuthentication = es2fa;
+                es2fa = NULL;
+        }
+
+        return EFI_SUCCESS;
+}
diff --git a/src/boot/efi/shim.h b/src/boot/efi/shim.h
new file mode 100644 (file)
index 0000000..2dcf48d
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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
+ * Lesser General Public License for more details.
+ *
+ * Port to systemd-boot
+ * Copyright 2017 Max Resch <resch.max@gmail.com>
+ *
+ * Security Policy Handling
+ * Copyright 2012 <James.Bottomley@HansenPartnership.com>
+ * https://github.com/mjg59/efitools
+ */
+
+#ifndef __SDBOOT_SHIM_H
+#define __SDBOOT_SHIM_H
+
+BOOLEAN shim_loaded(void);
+
+BOOLEAN secure_boot_enabled(void);
+
+EFI_STATUS security_policy_install(void);
+
+EFI_STATUS security_policy_uninstall(void);
+
+#endif