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: No, will be submitted to upstream
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.

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 |  210 ++++++++++++++++++++++++++++++
 arch/x86/boot/compressed/misc.c           |    2 
 arch/x86/boot/compressed/misc.h           |   18 +-
 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                       |   19 ++
 13 files changed, 378 insertions(+), 11 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
@@ -81,6 +81,7 @@ endif
 
 $(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,4 +1,4 @@
-#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
@@ -1021,6 +1021,9 @@ struct boot_params *efi_main(struct efi_
 
 	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,210 @@
+/* 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
+
+static void efi_printk_status(char *reason, efi_status_t status)
+{
+	efi_printk(s_table, reason);
+	efi_printk(s_table, 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
@@ -445,7 +445,7 @@ asmlinkage __visible void *extract_kerne
 	return output;
 }
 
-#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
@@ -64,22 +64,24 @@ int cmdline_find_option(const char *opti
 int cmdline_find_option_bool(const char *option);
 #endif
 
-
-#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
@@ -175,6 +175,16 @@ static inline bool efi_runtime_supported
 extern struct console early_efi_console;
 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
@@ -249,6 +259,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
@@ -8,6 +8,7 @@
 #define SETUP_PCI			3
 #define SETUP_EFI			4
 #define SETUP_APPLE_PROPERTIES		5
+#define SETUP_EFI_SECRET_KEY		6
 
 /* ram_size flags */
 #define RAMDISK_IMAGE_START_MASK	0x07FF
--- a/arch/x86/kernel/setup.c
+++ b/arch/x86/kernel/setup.c
@@ -411,10 +411,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) {
@@ -436,9 +448,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
@@ -155,6 +155,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
@@ -30,3 +30,4 @@ 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
--- /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
@@ -1656,4 +1656,23 @@ efi_status_to_str(efi_status_t status)
 
 	return "";
 }
+
+#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 */