Blob Blame History Raw
From f8e8b60ca567bfac000b76c37f420674d7a7f619 Mon Sep 17 00:00:00 2001
From: "Lee, Chun-Yi" <jlee@suse.com>
Date: Tue, 7 Jul 2015 15:09:06 +0800
Subject: [PATCH v2 14/16] PM / hibernate: Allow user trigger hibernation key
 re-generating

Patch-mainline: Not yet, reviewing on linux-efi
References: fate#316350

This patch provides a ioctl for triggering hibernation key re-generating
process. It's allow user call ioctl to raise the flag of key re-generating.
Kernel writes a flag to a efi runtime variable, the GUID is
S4SignKeyRegen-fe141863-c070-478e-b8a3-878a5dc9ef21, then EFI stub will
re-generates hibernation key when queried flag.

To aviod the hibernation key changes in hibernating cycle that causes hiberne
restoring failed, this flag is only available when system runs normal
reboot or shutdown. The hibernate code will clean the flag when it raised
in a hiberante cycle.

Joey Lee:
 - Using efi_call_physx() for v3.12 kernel.

Reviewed-by: Jiri Kosina <jkosina@suse.com>
Tested-by: Jiri Kosina <jkosina@suse.com>
Signed-off-by: Lee, Chun-Yi <jlee@suse.com>
---
 arch/x86/boot/compressed/eboot.c          |   21 +++++++++++++---
 arch/x86/power/hibernate_keys.c           |    2 +
 drivers/firmware/Makefile                 |    1 
 drivers/firmware/efi/Kconfig              |    4 +++
 drivers/firmware/efi/Makefile             |    1 
 drivers/firmware/efi/efi-hibernate_keys.c |   39 ++++++++++++++++++++++++++++++
 include/linux/suspend.h                   |   16 ++++++++++++
 include/uapi/linux/suspend_ioctls.h       |    3 +-
 kernel/power/Kconfig                      |    1 
 kernel/power/hibernate.c                  |    2 +
 kernel/power/user.c                       |   11 ++++++++
 kernel/reboot.c                           |    3 ++
 12 files changed, 100 insertions(+), 4 deletions(-)
 create mode 100644 drivers/firmware/efi/efi-hibernate_keys.c

--- a/arch/x86/boot/compressed/eboot.c
+++ b/arch/x86/boot/compressed/eboot.c
@@ -1446,10 +1446,12 @@ static void setup_hibernation_keys(struc
 {
 	unsigned long key_size;
 	unsigned long attributes;
+	unsigned long ignore;
 	struct setup_data *setup_data, *hibernation_setup_data;
 	struct hibernation_keys *keys;
+	bool regen_key = false;
 	unsigned long size = 0;
-	efi_status_t status;
+	efi_status_t status, reg_status;
 
 	/* Allocate setup_data to carry keys */
 	size = sizeof(struct setup_data) + sizeof(struct hibernation_keys);
@@ -1479,12 +1481,17 @@ static void setup_hibernation_keys(struc
 			goto clean_fail;
 	}
 
-	if (status != EFI_SUCCESS) {
-		efi_printk(sys_table, "Failed to get existing hibernation key\n");
+	reg_status = efi_call_early(get_variable, HIBERNATION_KEY_REGEN_FLAG,
+				    &EFI_HIBERNATION_GUID, &attributes, &ignore,
+				    &regen_key);
+	if ((status != EFI_SUCCESS) ||
+	   (reg_status == EFI_SUCCESS && regen_key)) {
+		efi_printk(sys_table, "Regenerating hibernation key\n");
 
 		efi_get_random_key(sys_table, params, keys->hibernation_key,
 				   HIBERNATION_DIGEST_SIZE);
 
+		/* Set new hibernation key to bootservice non-volatile variable */
 		status = efi_call_early(set_variable, HIBERNATION_KEY,
 					&EFI_HIBERNATION_GUID,
 					HIBERNATION_KEY_ATTRIBUTE,
@@ -1492,6 +1499,14 @@ static void setup_hibernation_keys(struc
 					keys->hibernation_key);
 		if (status != EFI_SUCCESS)
 			efi_printk(sys_table, "Failed to set hibernation key\n");
+
+		efi_call_early(get_variable, HIBERNATION_KEY,
+				&EFI_HIBERNATION_GUID,
+				&attributes, &key_size, keys->hibernation_key);
+
+		/* Clean key regenerate flag */
+		efi_call_early(set_variable, HIBERNATION_KEY_REGEN_FLAG,
+				&EFI_HIBERNATION_GUID, 0, 0, NULL);
 	}
 
 clean_fail:
--- a/arch/x86/power/hibernate_keys.c
+++ b/arch/x86/power/hibernate_keys.c
@@ -165,6 +165,8 @@ static int __init init_hibernation_keys(
 	memblock_free(keys_phys_addr, sizeof(struct hibernation_keys));
 	keys_phys_addr = 0;
 
+	set_hibernation_key_regen_flag = false;
+
 	return ret;
 }
 
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -23,3 +23,4 @@ obj-y				+= broadcom/
 obj-$(CONFIG_GOOGLE_FIRMWARE)	+= google/
 obj-$(CONFIG_EFI)		+= efi/
 obj-$(CONFIG_UEFI_CPER)		+= efi/
+obj-$(CONFIG_EFI_HIBERNATION_KEYS)	+= efi/
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -41,6 +41,10 @@ config EFI_VARS_PSTORE_DEFAULT_DISABLE
 	  backend for pstore by default. This setting can be overridden
 	  using the efivars module's pstore_disable parameter.
 
+config EFI_HIBERNATION_KEYS
+	bool
+	select EFI_VARS
+
 config EFI_RUNTIME_MAP
 	bool "Export efi runtime maps to sysfs"
 	depends on X86 && EFI && KEXEC_CORE
--- a/drivers/firmware/efi/Makefile
+++ b/drivers/firmware/efi/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_EFI)			+= capsule.o
 obj-$(CONFIG_EFI_VARS)			+= efivars.o
 obj-$(CONFIG_EFI_ESRT)			+= esrt.o
 obj-$(CONFIG_EFI_VARS_PSTORE)		+= efi-pstore.o
+obj-$(CONFIG_EFI_HIBERNATION_KEYS)	+= efi-hibernate_keys.o
 obj-$(CONFIG_UEFI_CPER)			+= cper.o
 obj-$(CONFIG_EFI_RUNTIME_MAP)		+= runtime-map.o
 obj-$(CONFIG_EFI_RUNTIME_WRAPPERS)	+= runtime-wrappers.o
--- /dev/null
+++ b/drivers/firmware/efi/efi-hibernate_keys.c
@@ -0,0 +1,39 @@
+/* EFI variable handler of hibernation key regen flag
+ *
+ * Copyright (C) 2015 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/slab.h>
+#include <linux/suspend.h>
+
+void create_hibernation_key_regen_flag(void)
+{
+	struct efivar_entry *entry = NULL;
+	int err = 0;
+
+	if (!set_hibernation_key_regen_flag)
+		return;
+
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return;
+
+	memcpy(entry->var.VariableName,
+		HIBERNATION_KEY_REGEN_FLAG, sizeof(HIBERNATION_KEY_REGEN_FLAG));
+	memcpy(&(entry->var.VendorGuid),
+		&EFI_HIBERNATION_GUID, sizeof(efi_guid_t));
+
+	err = efivar_entry_set(entry, HIBERNATION_KEY_SEED_ATTRIBUTE,
+				sizeof(bool), &set_hibernation_key_regen_flag, NULL);
+	if (err)
+		pr_warn("PM: Set flag of regenerating hibernation key failed: %d\n", err);
+
+	kfree(entry);
+}
+EXPORT_SYMBOL_GPL(create_hibernation_key_regen_flag);
--- a/include/linux/suspend.h
+++ b/include/linux/suspend.h
@@ -366,6 +366,11 @@ struct platform_hibernation_ops {
 
 #define EFI_HIBERNATION_GUID \
 	EFI_GUID(0xfe141863, 0xc070, 0x478e, 0xb8, 0xa3, 0x87, 0x8a, 0x5d, 0xc9, 0xef, 0x21)
+#define HIBERNATION_KEY_REGEN_FLAG \
+	((efi_char16_t [20]) { 'H', 'I', 'B', 'E', 'R', 'N', 'A', 'T', 'I', 'O', 'N', 'K', 'e', 'y', 'R', 'e', 'g', 'e', 'n', 0 })
+#define HIBERNATION_KEY_SEED_ATTRIBUTE	(EFI_VARIABLE_NON_VOLATILE | \
+					EFI_VARIABLE_BOOTSERVICE_ACCESS | \
+					EFI_VARIABLE_RUNTIME_ACCESS)
 
 /* HMAC Algorithm of Hibernate Signature */
 #define HIBERNATION_HMAC	"hmac(sha1)"
@@ -374,6 +379,16 @@ struct platform_hibernation_ops {
 /* kernel/power/hibernate.c */
 extern int sigenforce;
 
+/* kernel/power/user.c */
+extern bool set_hibernation_key_regen_flag;
+
+#ifdef CONFIG_HIBERNATE_VERIFICATION
+/* drivers/firmware/efi/efi-hibernate_keys.c */
+extern void create_hibernation_key_regen_flag(void);
+#else
+static inline void create_hibernation_key_regen_flag(void) {}
+#endif
+
 /* kernel/power/snapshot.c */
 extern void __register_nosave_region(unsigned long b, unsigned long e, int km);
 static inline void __init register_nosave_region(unsigned long b, unsigned long e)
@@ -406,6 +421,7 @@ static inline void hibernation_set_ops(c
 static inline int hibernate(void) { return -ENOSYS; }
 static inline bool system_entering_hibernation(void) { return false; }
 static inline bool hibernation_available(void) { return false; }
+static inline void create_hibernation_key_regen_flag(void) {}
 #endif /* CONFIG_HIBERNATION */
 
 /* Hibernation and suspend events */
--- a/include/uapi/linux/suspend_ioctls.h
+++ b/include/uapi/linux/suspend_ioctls.h
@@ -28,6 +28,7 @@ struct resume_swap_area {
 #define SNAPSHOT_PREF_IMAGE_SIZE	_IO(SNAPSHOT_IOC_MAGIC, 18)
 #define SNAPSHOT_AVAIL_SWAP_SIZE	_IOR(SNAPSHOT_IOC_MAGIC, 19, __kernel_loff_t)
 #define SNAPSHOT_ALLOC_SWAP_PAGE	_IOR(SNAPSHOT_IOC_MAGIC, 20, __kernel_loff_t)
-#define SNAPSHOT_IOC_MAXNR	20
+#define SNAPSHOT_REGENERATE_KEY		_IO(SNAPSHOT_IOC_MAGIC, 21)
+#define SNAPSHOT_IOC_MAXNR	21
 
 #endif /* _LINUX_SUSPEND_IOCTLS_H */
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -81,6 +81,7 @@ config HIBERNATE_VERIFICATION
 	depends on HIBERNATION
 	depends on EFI_STUB
 	depends on X86
+	select EFI_HIBERNATION_KEYS
 	select CRYPTO_HMAC
 	select CRYPTO_SHA1
 	help
--- a/kernel/power/hibernate.c
+++ b/kernel/power/hibernate.c
@@ -665,6 +665,8 @@ int hibernate(void)
 		return -EPERM;
 	}
 
+	set_hibernation_key_regen_flag = false;
+
 	lock_system_sleep();
 	/* The snapshot device should not be opened while we're running */
 	if (!atomic_add_unless(&snapshot_device_available, -1, 0)) {
--- a/kernel/power/user.c
+++ b/kernel/power/user.c
@@ -25,6 +25,7 @@
 #include <linux/cpu.h>
 #include <linux/freezer.h>
 #include <linux/security.h>
+#include <linux/efi.h>
 
 #include <linux/uaccess.h>
 
@@ -33,6 +34,9 @@
 
 #define SNAPSHOT_MINOR	231
 
+/* Handler will create HIBERNATIONKeyRegen EFI variable when flag raised */
+bool set_hibernation_key_regen_flag;
+
 static struct snapshot_data {
 	struct snapshot_handle handle;
 	int swap;
@@ -342,6 +346,8 @@ static long snapshot_ioctl(struct file *
 			error = -EPERM;
 			break;
 		}
+		/* clean flag to avoid hibernation key regenerated */
+		set_hibernation_key_regen_flag = false;
 		/*
 		 * Tasks are frozen and the notifiers have been called with
 		 * PM_HIBERNATION_PREPARE
@@ -355,6 +361,7 @@ static long snapshot_ioctl(struct file *
 		break;
 
 	case SNAPSHOT_POWER_OFF:
+		set_hibernation_key_regen_flag = false;
 		if (data->platform_support)
 			error = hibernation_platform_enter();
 		break;
@@ -390,6 +397,10 @@ static long snapshot_ioctl(struct file *
 		}
 		break;
 
+	case SNAPSHOT_REGENERATE_KEY:
+		set_hibernation_key_regen_flag = !!arg;
+		break;
+
 	default:
 		error = -ENOTTY;
 
--- a/kernel/reboot.c
+++ b/kernel/reboot.c
@@ -213,6 +213,7 @@ void migrate_to_reboot_cpu(void)
  */
 void kernel_restart(char *cmd)
 {
+	create_hibernation_key_regen_flag();
 	kernel_restart_prepare(cmd);
 	migrate_to_reboot_cpu();
 	syscore_shutdown();
@@ -240,6 +241,7 @@ static void kernel_shutdown_prepare(enum
  */
 void kernel_halt(void)
 {
+	create_hibernation_key_regen_flag();
 	kernel_shutdown_prepare(SYSTEM_HALT);
 	migrate_to_reboot_cpu();
 	syscore_shutdown();
@@ -256,6 +258,7 @@ EXPORT_SYMBOL_GPL(kernel_halt);
  */
 void kernel_power_off(void)
 {
+	create_hibernation_key_regen_flag();
 	kernel_shutdown_prepare(SYSTEM_POWER_OFF);
 	if (pm_power_off_prepare)
 		pm_power_off_prepare();