Blob Blame History Raw
From a52895e103adf8dcaf94f481dec8a6098caccc99 Mon Sep 17 00:00:00 2001
From: "Lee, Chun-Yi" <jlee@suse.com>
Date: Tue, 12 Dec 2017 12:57:50 +0800
Subject: [PATCH 05/11] efi: generate secret key in EFI boot environment
Patch-mainline: Never, SUSE-specific
References: fate#316350

When secure boot is enabled, only signed EFI execution can access
EFI boot service variable before ExitBootService. Which means the
EFI boot service variable is secure.

This patch add a function to EFI stub to generate a 512-bit random
number that it can be used as a secret key for HMAC or AES. This
secret key will be kept in EFI boot service variable. EFI stub
reads and transfers secret key to runtime kernel by setup data.

At runtime, the secret key will be kept in hidden area to prevent
leak from accessing by user space. Hibernation uses EFI secret key
to encrypt hidden area and sign the snapshot image.

Joey Lee:
The EFI secure key mechanism be rejected by kernel upstream because
- The entropy inputs in EFI boot stage are too weak for key generation.
  - SLE applied RDRAND (x86) or EFI_RNG_PROTOCOL to grab stronger entropy.
- The UEFI variable store was not designed with confidentiality in mind.
  Secure boot relies on Microsoft's Business interests. Microsoft doesn't
  use UEFI variables for confidentiality, so we shouldn't either.

References: https://lkml.org/lkml/2018/8/5/10
Signed-off-by: Lee, Chun-Yi <jlee@suse.com>
---
 arch/x86/include/asm/efi.h                    |   11 +
 arch/x86/include/uapi/asm/bootparam.h         |    1 
 arch/x86/kernel/setup.c                       |   19 ++
 drivers/firmware/efi/Kconfig                  |   16 +
 drivers/firmware/efi/Makefile                 |    1 
 drivers/firmware/efi/efi-secret-key.c         |  117 ++++++++++++++
 drivers/firmware/efi/libstub/Makefile         |    2 
 drivers/firmware/efi/libstub/efi_secret_key.c |  209 ++++++++++++++++++++++++++
 drivers/firmware/efi/libstub/x86-stub.c       |    3 
 include/linux/efi.h                           |   18 ++
 10 files changed, 396 insertions(+), 1 deletion(-)
 create mode 100644 arch/x86/boot/compressed/efi_secret_key.c
 create mode 100644 drivers/firmware/efi/efi-secret-key.c

--- a/arch/x86/include/asm/efi.h
+++ b/arch/x86/include/asm/efi.h
@@ -198,6 +198,14 @@ static inline bool efi_runtime_supported
 
 extern void parse_efi_setup(u64 phys_addr, u32 data_len);
 
+#ifdef CONFIG_EFI_SECRET_KEY
+extern void efi_setup_secret_key(struct boot_params *params);
+extern void parse_efi_secret_key_setup(u64 phys_addr, u32 data_len);
+#else
+static inline void efi_setup_secret_key(struct boot_params *params) {}
+static inline void parse_efi_secret_key_setup(u64 phys_addr, u32 data_len) {}
+#endif /* CONFIG_EFI_SECRET_KEY */
+
 extern void efi_thunk_runtime_setup(void);
 efi_status_t efi_set_virtual_address_map(unsigned long memory_map_size,
 					 unsigned long descriptor_size,
@@ -381,6 +389,9 @@ extern void efi_find_mirror(void);
 extern void efi_reserve_boot_services(void);
 #else
 static inline void parse_efi_setup(u64 phys_addr, u32 data_len) {}
+static inline void parse_efi_secret_key_setup(u64 phys_addr, u32 data_len) {}
+static inline void efi_setup_secret_key(efi_system_table_t *table,
+					struct boot_params *params) {}
 static inline bool efi_reboot_required(void)
 {
 	return false;
--- a/arch/x86/include/uapi/asm/bootparam.h
+++ b/arch/x86/include/uapi/asm/bootparam.h
@@ -11,6 +11,7 @@
 #define SETUP_APPLE_PROPERTIES		5
 #define SETUP_JAILHOUSE			6
 #define SETUP_CC_BLOB			7
+#define SETUP_EFI_SECRET_KEY		16
 
 #define SETUP_INDIRECT			(1<<31)
 
--- a/arch/x86/kernel/setup.c
+++ b/arch/x86/kernel/setup.c
@@ -335,10 +335,22 @@ static void __init reserve_initrd(void)
 }
 #endif /* CONFIG_BLK_DEV_INITRD */
 
+static void __init remove_setup_data(u64 pa_prev, u64 pa_next)
+{
+	struct setup_data *data;
+
+	if (pa_prev) {
+		data = early_memremap(pa_prev, sizeof(*data));
+		data->next = pa_next;
+		early_iounmap(data, sizeof(*data));
+	} else
+		boot_params.hdr.setup_data = pa_next;
+}
+
 static void __init parse_setup_data(void)
 {
 	struct setup_data *data;
-	u64 pa_data, pa_next;
+	u64 pa_data, pa_next, pa_prev = 0;
 
 	pa_data = boot_params.hdr.setup_data;
 	while (pa_data) {
@@ -360,9 +372,14 @@ static void __init parse_setup_data(void
 		case SETUP_EFI:
 			parse_efi_setup(pa_data, data_len);
 			break;
+		case SETUP_EFI_SECRET_KEY:
+			parse_efi_secret_key_setup(pa_data, data_len);
+			remove_setup_data(pa_prev, pa_next);
+			break;
 		default:
 			break;
 		}
+		pa_prev = pa_data;
 		pa_data = pa_next;
 	}
 }
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -309,6 +309,22 @@ config EFI_EMBEDDED_FIRMWARE
 	bool
 	select CRYPTO_LIB_SHA256
 
+config EFI_SECRET_KEY
+	bool "EFI secret key"
+	default n
+	depends on EFI_STUB && X86
+	select HIDDEN_AREA
+	help
+	  This option enables the EFI secret key function in EFI stub. EFI
+	  stub wll generate a 512-bit random number that it can be used as
+	  a secret key by HMAC or AES. The secret key will be kept in EFI
+	  oot service variable which is secure when secre boot is enabled.
+	  At runtime, the secret key will be kept in hidden area to prevent
+	  leak from accessing by user space. Hibernation uses EFI secret key
+	  to encrypt hidden area and sign the snapshot image.
+
+	  If unsure, say N.
+
 endmenu
 
 config UEFI_CPER
--- a/drivers/firmware/efi/Makefile
+++ b/drivers/firmware/efi/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_ARM64)			+= $(arm-obj-y)
 riscv-obj-$(CONFIG_EFI)			:= efi-init.o riscv-runtime.o
 obj-$(CONFIG_RISCV)			+= $(riscv-obj-y)
 obj-$(CONFIG_EFI_CAPSULE_LOADER)	+= capsule-loader.o
+obj-$(CONFIG_EFI_SECRET_KEY)		+= efi-secret-key.o
 obj-$(CONFIG_EFI_EARLYCON)		+= earlycon.o
 obj-$(CONFIG_UEFI_CPER_ARM)		+= cper-arm.o
 obj-$(CONFIG_UEFI_CPER_X86)		+= cper-x86.o
--- /dev/null
+++ b/drivers/firmware/efi/efi-secret-key.c
@@ -0,0 +1,117 @@
+/* EFI secret key
+ *
+ * Copyright (C) 2017 Lee, Chun-Yi <jlee@suse.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/efi.h>
+#include <linux/memblock.h>
+#include <linux/security.h>
+
+static u64 efi_skey_setup;
+static void *secret_key;
+
+#define EFI_STATUS_STR(_status) \
+	EFI_##_status : return "EFI_" __stringify(_status)
+
+const char *efi_status_to_str(efi_status_t status)
+{
+	switch (status) {
+	case EFI_STATUS_STR(SUCCESS);
+	case EFI_STATUS_STR(LOAD_ERROR);
+	case EFI_STATUS_STR(INVALID_PARAMETER);
+	case EFI_STATUS_STR(UNSUPPORTED);
+	case EFI_STATUS_STR(BAD_BUFFER_SIZE);
+	case EFI_STATUS_STR(BUFFER_TOO_SMALL);
+	case EFI_STATUS_STR(NOT_READY);
+	case EFI_STATUS_STR(DEVICE_ERROR);
+	case EFI_STATUS_STR(WRITE_PROTECTED);
+	case EFI_STATUS_STR(OUT_OF_RESOURCES);
+	case EFI_STATUS_STR(NOT_FOUND);
+	case EFI_STATUS_STR(ABORTED);
+	case EFI_STATUS_STR(SECURITY_VIOLATION);
+	}
+	/*
+	 * There are two possibilities for this message to be exposed:
+	 * - Caller feeds a unknown status code from firmware.
+	 * - A new status code be defined in efi.h but we forgot to update
+	 *   this function.
+	 */
+	return "Unknown efi status";
+}
+
+void __init parse_efi_secret_key_setup(u64 phys_addr, u32 data_len)
+{
+	struct setup_data *skey_setup_data;
+
+	/* reserve secret key setup data, will copy and erase later */
+	efi_skey_setup = phys_addr + sizeof(struct setup_data);
+	memblock_reserve(efi_skey_setup, sizeof(struct efi_skey_setup_data));
+
+	/* clean setup data */
+	skey_setup_data = early_memremap(phys_addr, data_len);
+	memset(skey_setup_data, 0, sizeof(struct setup_data));
+	early_iounmap(skey_setup_data, data_len);
+}
+
+static void __init
+print_efi_skey_setup_data(struct efi_skey_setup_data *skey_setup)
+{
+	pr_debug("EFI secret key detection status: %s 0x%lx\n",
+		efi_status_to_str(skey_setup->detect_status),
+		skey_setup->detect_status);
+	pr_debug("EFI secret key getting status: %s 0x%lx\n",
+		efi_status_to_str(skey_setup->final_status),
+		skey_setup->final_status);
+	pr_debug("EFI secret key size: %ld\n", skey_setup->key_size);
+
+	if (skey_setup->final_status == EFI_UNSUPPORTED)
+		pr_warn(KERN_CONT "EFI_RNG_PROTOCOL unavailable, hibernation will be lock-down.");
+	if (skey_setup->final_status == EFI_SUCCESS &&
+	    skey_setup->key_size < SECRET_KEY_SIZE) {
+		pr_warn(KERN_CONT "EFI secret key size %ld is less than %d.",
+			skey_setup->key_size, SECRET_KEY_SIZE);
+		pr_warn(KERN_CONT " Please regenerate secret key\n");
+	}
+}
+
+static int __init init_efi_secret_key(void)
+{
+	struct efi_skey_setup_data *skey_setup;
+	int ret = 0;
+
+	if (!efi_skey_setup)
+		return -ENODEV;
+
+	skey_setup = early_memremap(efi_skey_setup,
+				    sizeof(struct efi_skey_setup_data));
+	print_efi_skey_setup_data(skey_setup);
+	if ((skey_setup->final_status != EFI_SUCCESS) ||
+	    (skey_setup->key_size < SECRET_KEY_SIZE)) {
+		ret = -ENODEV;
+		goto out;
+	}
+	secret_key = memcpy_to_hidden_area(skey_setup->secret_key,
+					   SECRET_KEY_SIZE);
+	if (!secret_key)
+		pr_info("copy secret key to hidden area failed\n");
+
+out:
+	/* earse key in setup data */
+	memset(skey_setup->secret_key, 0, SECRET_KEY_SIZE);
+	early_iounmap(skey_setup, sizeof(struct efi_skey_setup_data));
+
+	return ret;
+}
+
+void *get_efi_secret_key(void)
+{
+	return secret_key;
+}
+EXPORT_SYMBOL(get_efi_secret_key);
+
+late_initcall(init_efi_secret_key);
--- a/drivers/firmware/efi/libstub/Makefile
+++ b/drivers/firmware/efi/libstub/Makefile
@@ -73,6 +73,8 @@ $(obj)/lib-%.o: $(srctree)/lib/%.c FORCE
 lib-$(CONFIG_EFI_GENERIC_STUB)	+= efi-stub.o fdt.o string.o \
 				   $(patsubst %.c,lib-%.o,$(efi-deps-y))
 
+lib-$(CONFIG_EFI_SECRET_KEY) += efi_secret_key.o
+
 lib-$(CONFIG_ARM)		+= arm32-stub.o
 lib-$(CONFIG_ARM64)		+= arm64-stub.o
 lib-$(CONFIG_X86)		+= x86-stub.o
--- /dev/null
+++ b/drivers/firmware/efi/libstub/efi_secret_key.c
@@ -0,0 +1,209 @@
+/* EFI secret key generator
+ *
+ * Copyright (C) 2021 Lee, Chun-Yi <jlee@suse.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/efi.h>
+#include <asm/efi.h>
+
+#include "efistub.h"
+
+static struct boot_params *b_params;
+
+#define EFI_STATUS_STR(_status) \
+	EFI_##_status : return "EFI_" __stringify(_status)
+
+const char *efi_status_to_str(efi_status_t status)
+{
+	switch (status) {
+	case EFI_STATUS_STR(SUCCESS);
+	case EFI_STATUS_STR(LOAD_ERROR);
+	case EFI_STATUS_STR(INVALID_PARAMETER);
+	case EFI_STATUS_STR(UNSUPPORTED);
+	case EFI_STATUS_STR(BAD_BUFFER_SIZE);
+	case EFI_STATUS_STR(BUFFER_TOO_SMALL);
+	case EFI_STATUS_STR(NOT_READY);
+	case EFI_STATUS_STR(DEVICE_ERROR);
+	case EFI_STATUS_STR(WRITE_PROTECTED);
+	case EFI_STATUS_STR(OUT_OF_RESOURCES);
+	case EFI_STATUS_STR(NOT_FOUND);
+	case EFI_STATUS_STR(ABORTED);
+	case EFI_STATUS_STR(SECURITY_VIOLATION);
+	}
+	/*
+	 * There are two possibilities for this message to be exposed:
+	 * - Caller feeds a unknown status code from firmware.
+	 * - A new status code be defined in efi.h but we forgot to update
+	 *   this function.
+	 */
+	return "Unknown efi status";
+}
+
+static void efi_printk_status(char *reason, efi_status_t status)
+{
+	efi_printk(reason);
+	efi_printk((char *)efi_status_to_str(status));
+	efi_printk("\n");
+}
+
+#define get_efi_var(name, vendor, ...) \
+	efi_rt_call(get_variable, (efi_char16_t *)(name), \
+		    (efi_guid_t *)(vendor), __VA_ARGS__)
+
+#define set_efi_var(name, vendor, ...) \
+	efi_rt_call(set_variable, (efi_char16_t *)(name), \
+		    (efi_guid_t *)(vendor), __VA_ARGS__)
+
+static efi_char16_t const secret_key_name[] = {
+	'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y', 0
+};
+#define SECRET_KEY_ATTRIBUTE	(EFI_VARIABLE_NON_VOLATILE | \
+				EFI_VARIABLE_BOOTSERVICE_ACCESS)
+
+static efi_status_t get_secret_key(u32 *attributes,
+			unsigned long *key_size,
+			struct efi_skey_setup_data *skey_setup)
+{
+	void *key_data;
+	efi_status_t status;
+
+	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
+				*key_size, &key_data);
+	if (status != EFI_SUCCESS) {
+		efi_printk_status("Failed to allocate mem: \n", status);
+		return status;
+	}
+	memset(key_data, 0, *key_size);
+	status = get_efi_var(secret_key_name, &EFI_SECRET_GUID,
+			     attributes, key_size, key_data);
+	if (status != EFI_SUCCESS) {
+		efi_printk_status("Failed to get secret key: ", status);
+		goto err;
+	}
+
+	memset(skey_setup->secret_key, 0, SECRET_KEY_SIZE);
+	memcpy(skey_setup->secret_key, key_data,
+	       (*key_size >= SECRET_KEY_SIZE) ? SECRET_KEY_SIZE : *key_size);
+err:
+	efi_bs_call(free_pool, key_data);
+	return status;
+}
+
+static efi_status_t remove_secret_key(u32 attributes)
+{
+	efi_status_t status;
+
+	status = set_efi_var(secret_key_name,
+			     &EFI_SECRET_GUID, attributes, 0, NULL);
+	if (status == EFI_SUCCESS)
+		efi_printk("Removed secret key\n");
+	else
+		efi_printk_status("Failed to remove secret key: ", status);
+
+	return status;
+}
+
+static efi_status_t create_secret_key(struct efi_skey_setup_data *skey_setup)
+{
+	efi_status_t status;
+
+	efi_printk("Create new secret key\n");
+	memset(skey_setup->secret_key, 0, SECRET_KEY_SIZE);
+	status = efi_get_random_bytes(SECRET_KEY_SIZE,
+				      (u8 *)skey_setup->secret_key);
+	if (status != EFI_SUCCESS) {
+		efi_printk_status("EFI_RNG_PROTOCOL unavailable, hibernation secret key is not generated: ", status);
+		status = EFI_UNSUPPORTED;
+		goto err;
+	}
+
+	status = set_efi_var(secret_key_name, &EFI_SECRET_GUID,
+			     SECRET_KEY_ATTRIBUTE, SECRET_KEY_SIZE,
+			     skey_setup->secret_key);
+	if (status != EFI_SUCCESS)
+		efi_printk_status("Failed to write secret key: ", status);
+
+err:
+	return status;
+}
+
+static efi_status_t regen_secret_key(struct efi_skey_setup_data *skey_setup)
+{
+	u32 attributes = 0;
+	unsigned long key_size = SECRET_KEY_SIZE;
+	efi_status_t status;
+
+	status = remove_secret_key(attributes);
+	if (status == EFI_SUCCESS)
+		status = create_secret_key(skey_setup);
+	if (status == EFI_SUCCESS)
+		status = get_secret_key(&attributes, &key_size, skey_setup);
+}
+
+void efi_setup_secret_key(struct boot_params *params)
+{
+	struct setup_data *setup_data, *skey_setup_data;
+	unsigned long setup_size = 0;
+	u32 attributes = 0;
+	unsigned long key_size = 0;
+	struct efi_skey_setup_data *skey_setup;
+	efi_status_t status;
+
+	b_params = params;
+
+	setup_size = sizeof(struct setup_data) + sizeof(struct efi_skey_setup_data);
+	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
+				setup_size, (void **)&skey_setup_data);
+	if (status != EFI_SUCCESS) {
+		efi_printk("Failed to allocate mem for secret key\n");
+		return;
+	}
+	memset(skey_setup_data, 0, setup_size);
+	skey_setup = (struct efi_skey_setup_data *) skey_setup_data->data;
+
+	/* detect the size of secret key variable */
+	status = get_efi_var(secret_key_name, &EFI_SECRET_GUID,
+			     &attributes, &key_size, NULL);
+	skey_setup->detect_status = status;
+	switch (status) {
+	case EFI_BUFFER_TOO_SMALL:
+		status = get_secret_key(&attributes, &key_size, skey_setup);
+		if (status != EFI_SUCCESS)
+			break;
+		if (attributes != SECRET_KEY_ATTRIBUTE) {
+			efi_printk("Found a unqualified secret key\n");
+			status = regen_secret_key(skey_setup);
+		}
+		break;
+
+	case EFI_NOT_FOUND:
+		status = create_secret_key(skey_setup);
+		if (status == EFI_SUCCESS) {
+			key_size = SECRET_KEY_SIZE;
+			status = get_secret_key(&attributes, &key_size, skey_setup);
+		}
+		break;
+
+	default:
+		efi_printk_status("Failed to detect secret key's size: ", status);
+	}
+
+	skey_setup->key_size = key_size;
+	skey_setup->final_status = status;
+
+	skey_setup_data->type = SETUP_EFI_SECRET_KEY;
+	skey_setup_data->len = sizeof(struct efi_skey_setup_data);
+	skey_setup_data->next = 0;
+	setup_data = (struct setup_data *)params->hdr.setup_data;
+	while (setup_data && setup_data->next)
+		setup_data = (struct setup_data *)setup_data->next;
+	if (setup_data)
+		setup_data->next = (unsigned long)skey_setup_data;
+	else
+		params->hdr.setup_data = (unsigned long)skey_setup_data;
+}
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -900,6 +900,9 @@ unsigned long efi_main(efi_handle_t hand
 
 	setup_efi_pci(boot_params);
 
+	if (boot_params->secure_boot == efi_secureboot_mode_enabled)
+		efi_setup_secret_key(boot_params);
+
 	setup_quirks(boot_params, bzimage_addr, buffer_end - buffer_start);
 
 	status = exit_boot(boot_params, handle);
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1364,4 +1364,22 @@ struct linux_efi_coco_secret_area {
 /* Header of a populated EFI secret area */
 #define EFI_SECRET_TABLE_HEADER_GUID	EFI_GUID(0x1e74f542, 0x71dd, 0x4d66,  0x96, 0x3e, 0xef, 0x42, 0x87, 0xff, 0x17, 0x3b)
 
+#ifdef CONFIG_EFI_SECRET_KEY
+#define EFI_SECRET_GUID \
+	EFI_GUID(0x8c136d32, 0x039a, 0x4016, 0x8b, 0xb4, 0x9e, 0x98, 0x5e, 0x62, 0x78, 0x6f)
+#define SECRET_KEY_SIZE        64
+struct efi_skey_setup_data {
+	unsigned long detect_status;
+	unsigned long final_status;
+	unsigned long key_size;
+	u8 secret_key[SECRET_KEY_SIZE];
+};
+extern void *get_efi_secret_key(void);
+#else
+#define SECRET_KEY_SIZE        0
+static inline void *get_efi_secret_key(void)
+{
+	return NULL;
+}
+#endif /* CONFIG_EFI_SECRET_KEY */
 #endif /* _LINUX_EFI_H */