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/boot/compressed/Makefile         |    1 
 arch/x86/boot/compressed/cpuflags.c       |    2 
 arch/x86/boot/compressed/eboot.c          |    3 
 arch/x86/boot/compressed/efi_secret_key.c |  239 ++++++++++++++++++++++++++++++
 arch/x86/boot/compressed/misc.c           |    2 
 arch/x86/boot/compressed/misc.h           |   17 +-
 arch/x86/include/asm/efi.h                |   13 +
 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     |   84 ++++++++++
 include/linux/efi.h                       |   18 ++
 13 files changed, 406 insertions(+), 10 deletions(-)
 create mode 100644 arch/x86/boot/compressed/efi_secret_key.c
 create mode 100644 drivers/firmware/efi/efi-secret-key.c

--- a/arch/x86/boot/compressed/Makefile
+++ b/arch/x86/boot/compressed/Makefile
@@ -88,6 +88,7 @@ vmlinux-objs-$(CONFIG_ACPI) += $(obj)/ac
 
 $(obj)/eboot.o: KBUILD_CFLAGS += -fshort-wchar -mno-red-zone
 
+vmlinux-objs-$(CONFIG_EFI_SECRET_KEY) += $(obj)/efi_secret_key.o
 vmlinux-objs-$(CONFIG_EFI_STUB) += $(obj)/eboot.o $(obj)/efi_stub_$(BITS).o \
 	$(objtree)/drivers/firmware/efi/libstub/lib.a
 vmlinux-objs-$(CONFIG_EFI_MIXED) += $(obj)/efi_thunk_$(BITS).o
--- a/arch/x86/boot/compressed/cpuflags.c
+++ b/arch/x86/boot/compressed/cpuflags.c
@@ -1,5 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0
-#ifdef CONFIG_RANDOMIZE_BASE
+#if defined(CONFIG_RANDOMIZE_BASE) || defined(CONFIG_EFI_SECRET_KEY)
 
 #include "../cpuflags.c"
 
--- a/arch/x86/boot/compressed/eboot.c
+++ b/arch/x86/boot/compressed/eboot.c
@@ -788,6 +788,9 @@ efi_main(struct efi_config *c, struct bo
 
 	setup_efi_pci(boot_params);
 
+	if (boot_params->secure_boot == efi_secureboot_mode_enabled)
+		efi_setup_secret_key(sys_table, boot_params);
+
 	setup_quirks(boot_params);
 
 	status = efi_call_early(allocate_pool, EFI_LOADER_DATA,
--- /dev/null
+++ b/arch/x86/boot/compressed/efi_secret_key.c
@@ -0,0 +1,239 @@
+/* EFI secret key generator
+ *
+ * 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 <asm/efi.h>
+
+#include "misc.h"
+
+static efi_system_table_t *s_table;
+static struct boot_params *b_params;
+
+#ifdef DEBUG
+#define debug_putstr(__x)  efi_printk(s_table, (char *)__x)
+#else
+#define debug_putstr(__x)
+#endif
+
+#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(s_table, reason);
+	efi_printk(s_table, (char *)efi_status_to_str(status));
+	efi_printk(s_table, "\n");
+}
+
+static unsigned long get_boot_seed(void)
+{
+	unsigned long hash = 0;
+
+	hash = rotate_xor(hash, build_str, sizeof(build_str));
+	hash = rotate_xor(hash, b_params, sizeof(*b_params));
+
+	return hash;
+}
+
+#include "../../lib/random.c"
+
+static void generate_secret_key(u8 key[], unsigned int size)
+{
+	unsigned int bfill = size;
+
+	if (key == NULL || !size)
+		return;
+
+	memset(key, 0, size);
+	while (bfill > 0) {
+		unsigned long entropy = 0;
+		unsigned int copy_len = 0;
+		entropy = get_random_long("EFI secret key");
+		copy_len = (bfill < sizeof(entropy)) ? bfill : sizeof(entropy);
+		memcpy((void *)(key + size - bfill), &entropy, copy_len);
+		bfill -= copy_len;
+	}
+}
+
+#define get_efi_var(name, vendor, ...) \
+	efi_call_runtime(get_variable, \
+			(efi_char16_t *)(name), (efi_guid_t *)(vendor), \
+			__VA_ARGS__);
+#define set_efi_var(name, vendor, ...) \
+	efi_call_runtime(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(unsigned long *attributes,
+			unsigned long *key_size,
+			struct efi_skey_setup_data *skey_setup)
+{
+	void *key_data;
+	efi_status_t status;
+
+	status = efi_call_early(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_call_early(free_pool, key_data);
+	return status;
+}
+
+static efi_status_t remove_secret_key(unsigned long attributes)
+{
+	efi_status_t status;
+
+	status = set_efi_var(secret_key_name,
+			     &EFI_SECRET_GUID, attributes, 0, NULL);
+	if (status == EFI_SUCCESS)
+		efi_printk(s_table, "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(s_table, "Create new secret key\n");
+	generate_secret_key(skey_setup->secret_key, SECRET_KEY_SIZE);
+	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);
+
+	return status;
+}
+
+static efi_status_t regen_secret_key(struct efi_skey_setup_data *skey_setup)
+{
+	unsigned long 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(efi_system_table_t *sys_table, struct boot_params *params)
+{
+	struct setup_data *setup_data, *skey_setup_data;
+	unsigned long setup_size = 0;
+	unsigned long attributes = 0;
+	unsigned long key_size = 0;
+	struct efi_skey_setup_data *skey_setup;
+	efi_status_t status;
+
+	s_table = sys_table;
+	b_params = params;
+
+	setup_size = sizeof(struct setup_data) + sizeof(struct efi_skey_setup_data);
+	status = efi_call_early(allocate_pool, EFI_LOADER_DATA,
+				setup_size, &skey_setup_data);
+	if (status != EFI_SUCCESS) {
+		efi_printk(s_table, "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(sys_table, "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/arch/x86/boot/compressed/misc.c
+++ b/arch/x86/boot/compressed/misc.c
@@ -449,7 +449,7 @@ void fortify_panic(const char *name)
 	error("detected buffer overflow");
 }
 
-#if CONFIG_RANDOMIZE_BASE
+#if defined(CONFIG_RANDOMIZE_BASE) || defined(CONFIG_EFI_SECRET_KEY)
 unsigned long rotate_xor(unsigned long hash, const void *area,
 			size_t size)
 {
--- a/arch/x86/boot/compressed/misc.h
+++ b/arch/x86/boot/compressed/misc.h
@@ -74,21 +74,24 @@ struct mem_vector {
 	unsigned long long size;
 };
 
-#if CONFIG_RANDOMIZE_BASE
+#if defined(CONFIG_RANDOMIZE_BASE) || defined(CONFIG_EFI_SECRET_KEY)
 #include <generated/compile.h>
 #include <generated/utsrelease.h>
-/* kaslr.c */
-void choose_random_location(unsigned long input,
-			    unsigned long input_size,
-			    unsigned long *output,
-			    unsigned long output_size,
-			    unsigned long *virt_addr);
 /* cpuflags.c */
 bool has_cpuflag(int flag);
 /* Simplified build-specific string for starting entropy. */
 static const char build_str[] = UTS_RELEASE " (" LINUX_COMPILE_BY "@"
 		LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION;
 unsigned long rotate_xor(unsigned long hash, const void *area, size_t size);
+#endif
+
+#if CONFIG_RANDOMIZE_BASE
+/* kaslr.c */
+void choose_random_location(unsigned long input,
+			    unsigned long input_size,
+			    unsigned long *output,
+			    unsigned long output_size,
+			    unsigned long *virt_addr);
 #else
 static inline void choose_random_location(unsigned long input,
 					  unsigned long input_size,
--- a/arch/x86/include/asm/efi.h
+++ b/arch/x86/include/asm/efi.h
@@ -172,6 +172,16 @@ 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(efi_system_table_t *table,
+				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(efi_system_table_t *table,
+					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 efifb_setup_from_dmi(struct screen_info *si, const char *opt);
 
 #ifdef CONFIG_EFI_MIXED
@@ -246,6 +256,9 @@ extern bool efi_is_table_address(unsigne
 
 #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
@@ -10,6 +10,7 @@
 #define SETUP_EFI			4
 #define SETUP_APPLE_PROPERTIES		5
 #define SETUP_JAILHOUSE			6
+#define SETUP_EFI_SECRET_KEY		7
 
 /* ram_size flags */
 #define RAMDISK_IMAGE_START_MASK	0x07FF
--- a/arch/x86/kernel/setup.c
+++ b/arch/x86/kernel/setup.c
@@ -398,10 +398,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) {
@@ -423,9 +435,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
@@ -193,6 +193,22 @@ config EFI_RCI2_TABLE
 
 	  Say Y here for Dell EMC PowerEdge systems.
 
+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
@@ -32,6 +32,7 @@ arm-obj-$(CONFIG_EFI)			:= arm-init.o ar
 obj-$(CONFIG_ARM)			+= $(arm-obj-y)
 obj-$(CONFIG_ARM64)			+= $(arm-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,84 @@
+/* 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;
+
+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_SUCCESS) {
+		pr_warn("EFI secret key getting failed: %s 0x%lx\n",
+			efi_status_to_str(skey_setup->final_status),
+			skey_setup->final_status);
+	}
+	if (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);
+	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");
+
+	/* 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/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1802,4 +1802,22 @@ struct linux_efi_memreserve {
 #define EFI_MEMRESERVE_COUNT(size) (((size) - sizeof(struct linux_efi_memreserve)) \
 	/ sizeof(((struct linux_efi_memreserve *)0)->entry[0]))
 
+#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 */
