| 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 | 114 ++++++++++++++++++++++++++ |
| drivers/firmware/efi/efi.c | 4 |
| drivers/firmware/efi/libstub/efi_secret_key.c | 36 ++++++++ |
| include/linux/efi.h | 11 ++ |
| 4 files changed, 165 insertions(+) |
| |
| |
| |
| @@ -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,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); |
| +} |
| |
| |
| @@ -412,6 +412,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) { |
| |
| |
| @@ -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; |
| |
| |
| |
| @@ -1291,6 +1291,11 @@ static inline struct efi_mokvar_table_en |
| #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; |
| @@ -1298,11 +1303,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 */ |