Blob Blame History Raw
From 9897578d93d596c652b72095adc27da55ed05106 Mon Sep 17 00:00:00 2001
From: "Lee, Chun-Yi" <jlee@suse.com>
Date: Tue, 12 Dec 2017 14:10:37 +0800
Subject: [PATCH 06/11] efi: allow user to regenerate secret key
Patch-mainline: Never, SUSE-specific
References: fate#316350

This patch adds a sysfs interface for user to trigger the secret key
regenerate process in EFI stub. At runtime, kernel creates SecretKeyRegen
efi variable as a flag. When system reboot, EFI stub will regenerate
secret key if the flga be found.

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>
---
 drivers/firmware/efi/efi-secret-key.c         |   96 ++++++++++++++++++++++++++
 drivers/firmware/efi/efi.c                    |    4 +
 drivers/firmware/efi/libstub/efi_secret_key.c |   36 +++++++++
 include/linux/efi.h                           |   11 ++
 4 files changed, 147 insertions(+)

--- a/drivers/firmware/efi/efi-secret-key.c
+++ b/drivers/firmware/efi/efi-secret-key.c
@@ -11,9 +11,12 @@
 #include <linux/efi.h>
 #include <linux/memblock.h>
 #include <linux/security.h>
+#include <linux/slab.h>
+#include <linux/kobject.h>
 
 static u64 efi_skey_setup;
 static void *secret_key;
+static bool skey_regen;
 
 #define EFI_STATUS_STR(_status) \
 	EFI_##_status : return "EFI_" __stringify(_status)
@@ -115,3 +118,96 @@ void *get_efi_secret_key(void)
 EXPORT_SYMBOL(get_efi_secret_key);
 
 late_initcall(init_efi_secret_key);
+
+static int set_regen_flag(void)
+{
+	efi_status_t status = EFI_UNSUPPORTED;
+	bool regen = true;
+
+	if (!efi_enabled(EFI_RUNTIME_SERVICES))
+		return 0;
+
+	if (efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE))
+		status = efi.set_variable(EFI_SECRET_KEY_REGEN, &EFI_SECRET_GUID,
+					  EFI_SECRET_KEY_REGEN_ATTRIBUTE,
+					  sizeof(bool), &regen);
+	if (status != EFI_SUCCESS)
+		pr_warn("Create EFI secret key regen failed: 0x%lx\n", status);
+
+	return efi_status_to_err(status);
+}
+
+static int clean_regen_flag(void)
+{
+	efi_status_t status = EFI_UNSUPPORTED;
+
+	if (!efi_enabled(EFI_RUNTIME_SERVICES))
+		return 0;
+
+	if (efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE))
+		status = efi.set_variable(EFI_SECRET_KEY_REGEN, &EFI_SECRET_GUID,
+					  EFI_SECRET_KEY_REGEN_ATTRIBUTE,
+				          0, NULL);
+	if (status != EFI_SUCCESS && status != EFI_NOT_FOUND)
+		pr_warn("Clean EFI secret key regen failed: 0x%lx\n", status);
+
+	return efi_status_to_err(status);
+}
+
+void efi_skey_stop_regen(void)
+{
+	if (!efi_enabled(EFI_RUNTIME_SERVICES))
+		return;
+
+	if (!clean_regen_flag())
+		skey_regen = false;
+}
+EXPORT_SYMBOL(efi_skey_stop_regen);
+
+static struct kobject *secret_key_kobj;
+
+static ssize_t regen_show(struct kobject *kobj,
+			  struct kobj_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%d\n", skey_regen);
+}
+
+static ssize_t regen_store(struct kobject *kobj,
+			   struct kobj_attribute *attr,
+			   const char *buf, size_t size)
+{
+	bool regen_in;
+	int ret;
+
+	ret = strtobool(buf, &regen_in);
+	if (ret < 0)
+		return ret;
+
+	if (!skey_regen && regen_in) {
+		ret = set_regen_flag();
+		if (ret < 0)
+			return ret;
+	}
+
+	if (skey_regen && !regen_in) {
+		ret = clean_regen_flag();
+		if (ret < 0)
+			return ret;
+	}
+
+	skey_regen = regen_in;
+
+	return size;
+}
+
+static const struct kobj_attribute regen_attr =
+	__ATTR(regen, 0644, regen_show, regen_store);
+
+int __init efi_skey_sysfs_init(struct kobject *efi_kobj)
+{
+	secret_key_kobj = kobject_create_and_add("secret-key", efi_kobj);
+	if (!secret_key_kobj)
+		return -ENOMEM;
+
+	return sysfs_create_file(secret_key_kobj, &regen_attr.attr);
+}
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -394,6 +394,10 @@ static int __init efisubsys_init(void)
 	if (error)
 		goto err_remove_group;
 
+	error = efi_skey_sysfs_init(efi_kobj);
+	if (error)
+		goto err_remove_group;
+
 	/* and the standard mountpoint for efivarfs */
 	error = sysfs_create_mount_point(efi_kobj, "efivars");
 	if (error) {
--- a/drivers/firmware/efi/libstub/efi_secret_key.c
+++ b/drivers/firmware/efi/libstub/efi_secret_key.c
@@ -132,6 +132,39 @@ err:
 	return status;
 }
 
+static bool found_regen_flag(void)
+{
+	u32 attributes = 0;
+	unsigned long size = 0;
+	void *flag;
+	bool regen;
+	efi_status_t status;
+
+	/* detect secret key regen flag variable */
+	status = get_efi_var(EFI_SECRET_KEY_REGEN, &EFI_SECRET_GUID,
+			     &attributes, &size, NULL);
+	if (status != EFI_BUFFER_TOO_SMALL)
+		return false;
+
+	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
+				size, &flag);
+	if (status != EFI_SUCCESS)
+		return false;
+
+	memset(flag, 0, size);
+	status = get_efi_var(EFI_SECRET_KEY_REGEN, &EFI_SECRET_GUID,
+			     &attributes, &size, flag);
+	if (status == EFI_SUCCESS)
+		regen = *(bool *)flag;
+
+	/* clean regen flag */
+	set_efi_var(EFI_SECRET_KEY_REGEN, &EFI_SECRET_GUID,
+		    attributes, 0, NULL);
+err:
+	efi_bs_call(free_pool, flag);
+	return regen;
+}
+
 static efi_status_t regen_secret_key(struct efi_skey_setup_data *skey_setup)
 {
 	u32 attributes = 0;
@@ -178,6 +211,9 @@ void efi_setup_secret_key(struct boot_pa
 		if (attributes != SECRET_KEY_ATTRIBUTE) {
 			efi_printk("Found a unqualified secret key\n");
 			status = regen_secret_key(skey_setup);
+		} else if (found_regen_flag()) {
+			efi_printk("Regenerate secret key\n");
+			status = regen_secret_key(skey_setup);
 		}
 		break;
 
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1331,6 +1331,11 @@ struct linux_efi_coco_secret_area {
 #define EFI_SECRET_GUID \
 	EFI_GUID(0x8c136d32, 0x039a, 0x4016, 0x8b, 0xb4, 0x9e, 0x98, 0x5e, 0x62, 0x78, 0x6f)
 #define SECRET_KEY_SIZE        64
+#define EFI_SECRET_KEY_REGEN \
+	((efi_char16_t [15]) { 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y', 'R', 'e', 'g', 'e', 'n', 0 })
+#define EFI_SECRET_KEY_REGEN_ATTRIBUTE (EFI_VARIABLE_NON_VOLATILE | \
+					EFI_VARIABLE_BOOTSERVICE_ACCESS | \
+					EFI_VARIABLE_RUNTIME_ACCESS)
 struct efi_skey_setup_data {
 	unsigned long detect_status;
 	unsigned long final_status;
@@ -1338,11 +1343,17 @@ struct efi_skey_setup_data {
 	u8 secret_key[SECRET_KEY_SIZE];
 };
 extern void *get_efi_secret_key(void);
+extern void efi_skey_stop_regen(void);
+extern void efi_skey_set_regen(void);
+extern int efi_skey_sysfs_init(struct kobject *efi_kobj);
 #else
 #define SECRET_KEY_SIZE        0
 static inline void *get_efi_secret_key(void)
 {
 	return NULL;
 }
+static inline void efi_skey_stop_regen(void) {}
+static inline void efi_skey_set_regen(void) {}
+static inline int efi_skey_sysfs_init(struct kobject *efi_kobj) { return 0; }
 #endif /* CONFIG_EFI_SECRET_KEY */
 #endif /* _LINUX_EFI_H */