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: No, will be submitted to upstream
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.
Signed-off-by: Lee, Chun-Yi <jlee@suse.com>
---
arch/x86/boot/compressed/efi_secret_key.c | 36 +++++++++
drivers/firmware/efi/efi-secret-key.c | 114 ++++++++++++++++++++++++++++++
drivers/firmware/efi/efi.c | 4 +
include/linux/efi.h | 11 ++
4 files changed, 165 insertions(+)
--- a/arch/x86/boot/compressed/efi_secret_key.c
+++ b/arch/x86/boot/compressed/efi_secret_key.c
@@ -132,6 +132,39 @@ static efi_status_t create_secret_key(st
return status;
}
+static bool found_regen_flag(void)
+{
+ unsigned long 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_call_early(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_call_early(free_pool, flag);
+ return regen;
+}
+
static efi_status_t regen_secret_key(struct efi_skey_setup_data *skey_setup)
{
unsigned long attributes = 0;
@@ -179,6 +212,9 @@ void efi_setup_secret_key(efi_system_tab
if (attributes != SECRET_KEY_ATTRIBUTE) {
efi_printk(sys_table, "Found a unqualified secret key\n");
status = regen_secret_key(skey_setup);
+ } else if (found_regen_flag()) {
+ efi_printk(sys_table, "Regenerate secret key\n");
+ status = regen_secret_key(skey_setup);
}
break;
--- 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;
void __init parse_efi_secret_key_setup(u64 phys_addr, u32 data_len)
{
@@ -82,3 +85,114 @@ void *get_efi_secret_key(void)
EXPORT_SYMBOL(get_efi_secret_key);
late_initcall(init_efi_secret_key);
+
+static int set_regen_flag(void)
+{
+ struct efivar_entry *entry = NULL;
+ bool regen = true;
+ int err = 0;
+
+ if (!efi_enabled(EFI_RUNTIME_SERVICES))
+ return 0;
+
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ memcpy(entry->var.VariableName,
+ EFI_SECRET_KEY_REGEN, sizeof(EFI_SECRET_KEY_REGEN));
+ memcpy(&(entry->var.VendorGuid),
+ &EFI_SECRET_GUID, sizeof(efi_guid_t));
+ err = efivar_entry_set(entry, EFI_SECRET_KEY_REGEN_ATTRIBUTE,
+ sizeof(bool), ®en, NULL);
+ if (err)
+ pr_warn("Create EFI secret key regen failed: %d\n", err);
+
+ kfree(entry);
+
+ return err;
+}
+
+static int clean_regen_flag(void)
+{
+ struct efivar_entry *entry = NULL;
+ int err = 0;
+
+ if (!efi_enabled(EFI_RUNTIME_SERVICES))
+ return 0;
+
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ memcpy(entry->var.VariableName,
+ EFI_SECRET_KEY_REGEN, sizeof(EFI_SECRET_KEY_REGEN));
+ memcpy(&(entry->var.VendorGuid),
+ &EFI_SECRET_GUID, sizeof(efi_guid_t));
+ err = efivar_entry_set(entry, EFI_SECRET_KEY_REGEN_ATTRIBUTE,
+ 0, NULL, NULL);
+ if (err && err != -ENOENT)
+ pr_warn("Clean EFI secret key regen failed: %d\n", err);
+
+ kfree(entry);
+
+ return err;
+}
+
+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, ®en_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, ®en_attr.attr);
+}
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -351,6 +351,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/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1622,6 +1622,11 @@ efi_status_to_str(efi_status_t status)
#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;
@@ -1629,11 +1634,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 */