Blob Blame History Raw
From 71d8a8950aaf2fc5ed81ce75106e62955b835153 Mon Sep 17 00:00:00 2001
From: "Lee, Chun-Yi" <jlee@suse.com>
Date: Tue, 12 Dec 2017 16:37:58 +0800
Subject: [PATCH 08/11] PM / hibernate: Generate and verify signature for
 snapshot image
Patch-mainline: No, will be submitted to upstream
References: fate#316350

When producing memory snapshot image, hibernation uses HMAC-SHA512
with EFI secret key to calculate the hash for all data pages in image.
The hash result will be kept in the snapshot header as the image
signature. Before hibernation restores system, kernel executes
HMAC-SHA512 again and compares the result with the signature in header
to verify the integrity of snapshot image.

If the verification failed, the resume process will be stopped. The
snapshot image will be discarded and system will boot as normal.

Signed-off-by: Lee, Chun-Yi <jlee@suse.com>
---
 kernel/power/Kconfig     |   13 ++
 kernel/power/hibernate.c |    9 +
 kernel/power/power.h     |   14 ++
 kernel/power/snapshot.c  |  277 ++++++++++++++++++++++++++++++++++++++++++++---
 kernel/power/swap.c      |    5 
 kernel/power/user.c      |    4 
 6 files changed, 307 insertions(+), 15 deletions(-)

--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -76,6 +76,19 @@ config HIBERNATION
 
 	  For more information take a look at <file:Documentation/power/swsusp.txt>.
 
+config HIBERNATE_VERIFICATION
+	bool "Hibernate verification"
+	depends on HIBERNATION
+	depends on EFI_SECRET_KEY
+	select CRYPTO_HMAC
+	select CRYPTO_SHA512
+	help
+	  This option provides support for generating and verifying the
+	  signature of memory snapshot image by HMAC-SHA512. Current mechanism
+	  relies on UEFI secure boot environment, EFI stub generates HMAC
+	  key for hibernate verification. This function will be bypassed on
+	  legacy BIOS.
+
 config ARCH_SAVE_PAGE_KEYS
 	bool
 
--- a/kernel/power/hibernate.c
+++ b/kernel/power/hibernate.c
@@ -273,10 +273,14 @@ static int create_image(int platform_mod
 {
 	int error;
 
+	error = swsusp_prepare_hash(false);
+	if (error)
+		return error;
+
 	error = dpm_suspend_end(PMSG_FREEZE);
 	if (error) {
 		pr_err("Some devices failed to power down, aborting hibernation\n");
-		return error;
+		goto finish_hash;
 	}
 
 	error = platform_pre_snapshot(platform_mode);
@@ -330,6 +334,9 @@ static int create_image(int platform_mod
 	dpm_resume_start(in_suspend ?
 		(error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE);
 
+ finish_hash:
+	swsusp_finish_hash();
+
 	return error;
 }
 
--- a/kernel/power/power.h
+++ b/kernel/power/power.h
@@ -4,6 +4,10 @@
 #include <linux/freezer.h>
 #include <linux/compiler.h>
 
+/* HMAC algorithm for hibernate snapshot signature */
+#define SNAPSHOT_HMAC	"hmac(sha512)"
+#define SNAPSHOT_DIGEST_SIZE 64
+
 struct swsusp_info {
 	struct new_utsname	uts;
 	u32			version_code;
@@ -13,6 +17,7 @@ struct swsusp_info {
 	unsigned long		pages;
 	unsigned long		size;
 	unsigned long           trampoline_pfn;
+	u8                      signature[SNAPSHOT_DIGEST_SIZE];
 } __aligned(PAGE_SIZE);
 
 #ifdef CONFIG_HIBERNATION
@@ -162,6 +167,15 @@ extern int snapshot_create_trampoline(vo
 extern void snapshot_init_trampoline(void);
 extern void snapshot_restore_trampoline(void);
 extern void snapshot_free_trampoline(void);
+#ifdef CONFIG_HIBERNATE_VERIFICATION
+extern int snapshot_image_verify(void);
+extern int swsusp_prepare_hash(bool may_sleep);
+extern void swsusp_finish_hash(void);
+#else
+static inline int snapshot_image_verify(void) { return 0; }
+static inline int swsusp_prepare_hash(bool may_sleep) { return 0; }
+static inline void swsusp_finish_hash(void) {}
+#endif
 
 /* If unset, the snapshot device cannot be open. */
 extern atomic_t snapshot_device_available;
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -32,6 +32,7 @@
 #include <linux/ktime.h>
 #include <linux/security.h>
 #include <linux/efi.h>
+#include <linux/vmalloc.h>
 
 #include <linux/uaccess.h>
 #include <asm/mmu_context.h>
@@ -41,6 +42,9 @@
 #ifdef CONFIG_ARCH_HAS_SET_MEMORY
 #include <asm/set_memory.h>
 #endif
+#ifdef CONFIG_HIBERNATE_VERIFICATION
+#include <crypto/hash.h>
+#endif
 
 #include "power.h"
 
@@ -86,6 +90,7 @@ static inline void hibernate_restore_unp
  * to image kernel.
  */
 struct trampoline {
+	int sig_verify_ret;
 	bool secret_key_valid;
 	u8 secret_key[SECRET_KEY_SIZE];
 };
@@ -1418,8 +1423,241 @@ static inline void copy_data_page(unsign
 }
 #endif /* CONFIG_HIGHMEM */
 
-static void copy_data_pages(struct memory_bitmap *copy_bm,
-			    struct memory_bitmap *orig_bm)
+/* Total number of image pages */
+static unsigned int nr_copy_pages;
+
+/* Point array for collecting buffers' address in snapshot_write_next() */
+static void **h_buf;
+
+#ifdef CONFIG_HIBERNATE_VERIFICATION
+/*
+ * Signature of snapshot image
+ */
+static u8 signature[SNAPSHOT_DIGEST_SIZE];
+
+/* Keep the signature verification result for trampoline */
+static int sig_verify_ret;
+
+static u8 *s4_verify_digest;
+static struct shash_desc *s4_verify_desc;
+
+int swsusp_prepare_hash(bool may_sleep)
+{
+	struct crypto_shash *tfm;
+	u8 *key;
+	size_t digest_size, desc_size;
+	int ret;
+
+	key = get_efi_secret_key();
+	if (!key) {
+		pr_warn_once("PM: secret key is invalid\n");
+		return -EINVAL;
+	}
+
+	tfm = crypto_alloc_shash(SNAPSHOT_HMAC, 0, 0);
+	if (IS_ERR(tfm)) {
+		pr_err("PM: Allocate HMAC failed: %ld\n", PTR_ERR(tfm));
+		return PTR_ERR(tfm);
+	}
+
+	ret = crypto_shash_setkey(tfm, key, SNAPSHOT_DIGEST_SIZE);
+	if (ret) {
+		pr_err("PM: Set HMAC key failed\n");
+		goto error;
+	}
+
+	desc_size = crypto_shash_descsize(tfm) + sizeof(*s4_verify_desc);
+	digest_size = crypto_shash_digestsize(tfm);
+	s4_verify_digest = kzalloc(digest_size + desc_size, GFP_KERNEL);
+	if (!s4_verify_digest) {
+		pr_err("PM: Allocate digest failed\n");
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	s4_verify_desc = (void *) s4_verify_digest + digest_size;
+	s4_verify_desc->tfm = tfm;
+	if (may_sleep)
+		s4_verify_desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
+	ret = crypto_shash_init(s4_verify_desc);
+	if (ret < 0)
+		goto free_shash;
+
+	return 0;
+
+ free_shash:
+	kfree(s4_verify_digest);
+ error:
+	crypto_free_shash(tfm);
+	s4_verify_digest = NULL;
+	s4_verify_desc = NULL;
+	return ret;
+}
+
+void swsusp_finish_hash(void)
+{
+	if (s4_verify_desc)
+		crypto_free_shash(s4_verify_desc->tfm);
+	kfree(s4_verify_digest);
+	s4_verify_desc = NULL;
+	s4_verify_digest = NULL;
+}
+
+int snapshot_image_verify(void)
+{
+	int ret, i;
+
+	if (!h_buf) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	if (!efi_enabled(EFI_BOOT)) {
+		pr_info_once("PM: Bypass verification on non-EFI machine\n");
+		ret = 0;
+		goto error_prep;
+	}
+
+	ret = swsusp_prepare_hash(true);
+	if (ret || !s4_verify_desc)
+		goto error_prep;
+
+	for (i = 0; i < nr_copy_pages; i++) {
+		ret = crypto_shash_update(s4_verify_desc, *(h_buf + i), PAGE_SIZE);
+		if (ret)
+			goto error_shash;
+	}
+
+	ret = crypto_shash_final(s4_verify_desc, s4_verify_digest);
+	if (ret)
+		goto error_shash;
+
+	pr_debug("PM: Signature %*phN\n", SNAPSHOT_DIGEST_SIZE, signature);
+	pr_debug("PM: Digest    %*phN\n", SNAPSHOT_DIGEST_SIZE, s4_verify_digest);
+	if (memcmp(signature, s4_verify_digest, SNAPSHOT_DIGEST_SIZE))
+		ret = -EKEYREJECTED;
+
+ error_shash:
+	swsusp_finish_hash();
+
+ error_prep:
+	vfree(h_buf);
+	if (ret)
+		pr_warn("PM: Signature verification failed: %d\n", ret);
+ error:
+	sig_verify_ret = ret;
+	return ret;
+}
+
+static int
+__copy_data_pages(struct memory_bitmap *copy_bm, struct memory_bitmap *orig_bm)
+{
+	unsigned long pfn, dst_pfn;
+	struct page *d_page;
+	void *hash_buffer = NULL;
+	int ret = 0;
+
+	memory_bm_position_reset(orig_bm);
+	memory_bm_position_reset(copy_bm);
+	for (;;) {
+		pfn = memory_bm_next_pfn(orig_bm);
+		if (unlikely(pfn == BM_END_OF_MAP))
+			break;
+		dst_pfn = memory_bm_next_pfn(copy_bm);
+		copy_data_page(dst_pfn, pfn);
+
+		/* Generate digest */
+		d_page = pfn_to_page(dst_pfn);
+		if (PageHighMem(d_page)) {
+			void *kaddr = kmap_atomic(d_page);
+
+			copy_page(buffer, kaddr);
+			kunmap_atomic(kaddr);
+			hash_buffer = buffer;
+		} else {
+			hash_buffer = page_address(d_page);
+		}
+
+		if (!s4_verify_desc)
+			continue;
+
+		ret = crypto_shash_update(s4_verify_desc, hash_buffer,
+					  PAGE_SIZE);
+		if (ret)
+			return ret;
+	}
+
+	if (s4_verify_desc) {
+		ret = crypto_shash_final(s4_verify_desc, s4_verify_digest);
+		if (ret)
+			return ret;
+
+		memset(signature, 0, SNAPSHOT_DIGEST_SIZE);
+		memcpy(signature, s4_verify_digest, SNAPSHOT_DIGEST_SIZE);
+	}
+
+	return 0;
+}
+
+static void alloc_h_buf(void)
+{
+	h_buf = vmalloc(sizeof(void *) * nr_copy_pages);
+	if (!h_buf)
+		pr_err("PM: Allocate buffer point array failed\n");
+}
+
+static void init_signature(struct swsusp_info *info)
+{
+	memcpy(info->signature, signature, SNAPSHOT_DIGEST_SIZE);
+}
+
+static void load_signature(struct swsusp_info *info)
+{
+	memset(signature, 0, SNAPSHOT_DIGEST_SIZE);
+	memcpy(signature, info->signature, SNAPSHOT_DIGEST_SIZE);
+}
+
+static void init_sig_verify(struct trampoline *t)
+{
+	t->sig_verify_ret = sig_verify_ret;
+	sig_verify_ret = 0;
+}
+
+static void handle_sig_verify(struct trampoline *t)
+{
+	if (t->sig_verify_ret)
+		pr_warn("PM: Signature verification failed: %d\n",
+			t->sig_verify_ret);
+	else if (t->secret_key_valid)
+		pr_info("PM: Signature verification passed.\n");
+}
+#else
+static int
+__copy_data_pages(struct memory_bitmap *copy_bm, struct memory_bitmap *orig_bm)
+{
+	unsigned long pfn;
+
+	memory_bm_position_reset(orig_bm);
+	memory_bm_position_reset(copy_bm);
+	for (;;) {
+		pfn = memory_bm_next_pfn(orig_bm);
+		if (unlikely(pfn == BM_END_OF_MAP))
+			break;
+		copy_data_page(memory_bm_next_pfn(copy_bm), pfn);
+	}
+
+	return 0;
+}
+
+static inline void alloc_h_buf(void) {}
+static void init_signature(struct swsusp_info *info) {}
+static void load_signature(struct swsusp_info *info) {}
+static void init_sig_verify(struct trampoline *t) {}
+static void handle_sig_verify(struct trampoline *t) {}
+#endif /* CONFIG_HIBERNATE_VERIFICATION */
+
+static int copy_data_pages(struct memory_bitmap *copy_bm,
+			   struct memory_bitmap *orig_bm)
 {
 	struct zone *zone;
 	unsigned long pfn;
@@ -1433,18 +1671,9 @@ static void copy_data_pages(struct memor
 			if (page_is_saveable(zone, pfn))
 				memory_bm_set_bit(orig_bm, pfn);
 	}
-	memory_bm_position_reset(orig_bm);
-	memory_bm_position_reset(copy_bm);
-	for(;;) {
-		pfn = memory_bm_next_pfn(orig_bm);
-		if (unlikely(pfn == BM_END_OF_MAP))
-			break;
-		copy_data_page(memory_bm_next_pfn(copy_bm), pfn);
-	}
+	return __copy_data_pages(copy_bm, orig_bm);
 }
 
-/* Total number of image pages */
-static unsigned int nr_copy_pages;
 /* Number of pages needed for saving the original pfns of the image pages */
 static unsigned int nr_meta_pages;
 /*
@@ -1989,6 +2218,7 @@ static int swsusp_alloc(struct memory_bi
 asmlinkage __visible int swsusp_save(void)
 {
 	unsigned int nr_pages, nr_highmem;
+	int ret;
 
 	printk(KERN_INFO "PM: Creating hibernation image:\n");
 
@@ -2012,7 +2242,11 @@ asmlinkage __visible int swsusp_save(voi
 	 * Kill them.
 	 */
 	drain_local_pages(NULL);
-	copy_data_pages(&copy_bm, &orig_bm);
+	ret = copy_data_pages(&copy_bm, &orig_bm);
+	if (ret) {
+		pr_err("PM: Copy data pages failed\n");
+		return ret;
+	}
 
 	/*
 	 * End of critical section. From now on, we can write to memory,
@@ -2068,6 +2302,7 @@ static int init_header(struct swsusp_inf
 	info->size = info->pages;
 	info->size <<= PAGE_SHIFT;
 	info->trampoline_pfn = page_to_pfn(virt_to_page(trampoline_virt));
+	init_signature(info);
 	return init_header_complete(info);
 }
 
@@ -2116,6 +2351,8 @@ void snapshot_init_trampoline(void)
 	memset(trampoline_buff, 0, PAGE_SIZE);
 	t = (struct trampoline *)trampoline_buff;
 
+	init_sig_verify(t);
+
 	efi_secret_key = get_efi_secret_key();
 	if (efi_secret_key) {
 		memset(t->secret_key, 0, SECRET_KEY_SIZE);
@@ -2143,6 +2380,9 @@ void snapshot_restore_trampoline(void)
 	}
 
 	t = (struct trampoline *)trampoline_virt;
+
+	handle_sig_verify(t);
+
 	if (t->secret_key_valid) {
 		ret = decrypt_restore_hidden_area(t->secret_key, SECRET_KEY_SIZE);
 		if (ret)
@@ -2317,6 +2557,7 @@ static int load_header(struct swsusp_inf
 		nr_copy_pages = info->image_pages;
 		nr_meta_pages = info->pages - info->image_pages - 1;
 		trampoline_pfn = info->trampoline_pfn;
+		load_signature(info);
 	}
 	return error;
 }
@@ -2735,6 +2976,12 @@ int snapshot_write_next(struct snapshot_
 
 		safe_pages_list = NULL;
 
+		/* Allocate buffer point array for generating
+		 * digest to compare with signature.
+		 * h_buf will freed in snapshot_image_verify().
+		 */
+		alloc_h_buf();
+
 		error = memory_bm_create(&copy_bm, GFP_ATOMIC, PG_ANY);
 		if (error)
 			return error;
@@ -2762,6 +3009,8 @@ int snapshot_write_next(struct snapshot_
 			handle->sync_read = 0;
 			if (IS_ERR(handle->buffer))
 				return PTR_ERR(handle->buffer);
+			if (h_buf)
+				*h_buf = handle->buffer;
 		}
 	} else {
 		copy_last_highmem_page();
@@ -2776,6 +3025,8 @@ int snapshot_write_next(struct snapshot_
 		/* Capture the trampoline for transfer data */
 		if (pfn == trampoline_pfn && trampoline_pfn)
 			trampoline_buff = handle->buffer;
+		if (h_buf)
+			*(h_buf + (handle->cur - nr_meta_pages - 1)) = handle->buffer;
 	}
 	handle->cur++;
 	return PAGE_SIZE;
--- a/kernel/power/swap.c
+++ b/kernel/power/swap.c
@@ -1101,7 +1101,8 @@ static int load_image(struct swap_map_ha
 		snapshot_write_finalize(snapshot);
 		if (!snapshot_image_loaded(snapshot))
 			ret = -ENODATA;
-
+		if (!ret)
+			ret = snapshot_image_verify();
 		snapshot_init_trampoline();
 		/* clean the hidden area in boot kernel */
 		clean_hidden_area();
@@ -1466,6 +1467,8 @@ out_finish:
 				}
 			}
 		}
+		if (!ret)
+			ret = snapshot_image_verify();
 		snapshot_init_trampoline();
 		/* clean the hidden area in boot kernel */
 		clean_hidden_area();
--- a/kernel/power/user.c
+++ b/kernel/power/user.c
@@ -280,6 +280,10 @@ static long snapshot_ioctl(struct file *
 			error = -EPERM;
 			break;
 		}
+		if (snapshot_image_verify()) {
+			error = -EPERM;
+			break;
+		}
 		snapshot_init_trampoline();
 		/* clean the hidden area in boot kernel */
 		clean_hidden_area();