In current solution, the snapshot signature check used the RSA key-pair
that are generated by bootloader(e.g. shim) and pass the key-pair to
kernel through EFI variables. I choice to binding the snapshot
signature check mechanism with UEFI secure boot for provide stronger
protection of hibernate. Current behavior is following:
+ UEFI Secure Boot ON, Kernel found key-pair from shim:
Will do the S4 signature check.
+ UEFI Secure Boot ON, Kernel didn't find key-pair from shim:
Will lock down S4 function.
+ UEFI Secure Boot OFF
Will NOT do the S4 signature check.
Ignore any keys from bootloader.
Signed-off-by: Lee, Chun-Yi
---
kernel/power/hibernate.c | 35 +++++++++++++++++
kernel/power/main.c | 11 +++++-
kernel/power/snapshot.c | 95 ++++++++++++++++++++++++++--------------------
kernel/power/swap.c | 4 +-
kernel/power/user.c | 11 +++++
5 files changed, 112 insertions(+), 44 deletions(-)
Index: linux/kernel/power/hibernate.c
===================================================================
--- linux.orig/kernel/power/hibernate.c
+++ linux/kernel/power/hibernate.c
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#include "power.h"
@@ -634,6 +635,14 @@ int hibernate(void)
int error;
int skey_error;
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+ if (!capable(CAP_COMPROMISE_KERNEL) && !sign_key_data_loaded()) {
+#else
+ if (!capable(CAP_COMPROMISE_KERNEL)) {
+#endif
+ return -EPERM;
+ }
+
lock_system_sleep();
/* The snapshot device should not be opened while we're running */
if (!atomic_add_unless(&snapshot_device_available, -1, 0)) {
@@ -801,6 +810,15 @@ static int software_resume(void)
if (error)
goto Unlock;
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+ if (!capable(CAP_COMPROMISE_KERNEL) && find_wake_key_data()) {
+#else
+ if (!capable(CAP_COMPROMISE_KERNEL)) {
+#endif
+ mutex_unlock(&pm_mutex);
+ return -EPERM;
+ }
+
/* The snapshot device should not be opened while we're running */
if (!atomic_add_unless(&snapshot_device_available, -1, 0)) {
error = -EBUSY;
@@ -896,6 +914,15 @@ static ssize_t disk_show(struct kobject
int i;
char *start = buf;
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+ if (efi_enabled(EFI_SECURE_BOOT) && !sign_key_data_loaded()) {
+#else
+ if (efi_enabled(EFI_SECURE_BOOT)) {
+#endif
+ buf += sprintf(buf, "[%s]\n", "disabled");
+ return buf-start;
+ }
+
for (i = HIBERNATION_FIRST; i <= HIBERNATION_MAX; i++) {
if (!hibernation_modes[i])
continue;
@@ -930,6 +957,14 @@ static ssize_t disk_store(struct kobject
char *p;
int mode = HIBERNATION_INVALID;
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+ if (!capable(CAP_COMPROMISE_KERNEL) && !sign_key_data_loaded()) {
+#else
+ if (!capable(CAP_COMPROMISE_KERNEL)) {
+#endif
+ return -EPERM;
+ }
+
p = memchr(buf, '\n', n);
len = p ? p - buf : n;
Index: linux/kernel/power/main.c
===================================================================
--- linux.orig/kernel/power/main.c
+++ linux/kernel/power/main.c
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
#include "power.h"
@@ -301,7 +302,15 @@ static ssize_t state_show(struct kobject
}
#endif
#ifdef CONFIG_HIBERNATION
- s += sprintf(s, "%s\n", "disk");
+ if (!efi_enabled(EFI_SECURE_BOOT)) {
+ s += sprintf(s, "%s\n", "disk");
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+ } else if (sign_key_data_loaded()) {
+ s += sprintf(s, "%s\n", "disk");
+#endif
+ } else {
+ s += sprintf(s, "\n");
+ }
#else
if (s != buf)
/* convert the last space to a newline */
Index: linux/kernel/power/snapshot.c
===================================================================
--- linux.orig/kernel/power/snapshot.c
+++ linux/kernel/power/snapshot.c
@@ -860,7 +860,8 @@ static struct page *saveable_highmem_pag
BUG_ON(!PageHighMem(page));
- if (swsusp_page_is_sign_key(page))
+ if (!capable(CAP_COMPROMISE_KERNEL) &&
+ swsusp_page_is_sign_key(page))
return NULL;
if (swsusp_page_is_forbidden(page) || swsusp_page_is_free(page) ||
@@ -925,7 +926,8 @@ static struct page *saveable_page(struct
BUG_ON(PageHighMem(page));
- if (swsusp_page_is_sign_key(page))
+ if (!capable(CAP_COMPROMISE_KERNEL) &&
+ swsusp_page_is_sign_key(page))
return NULL;
if (swsusp_page_is_forbidden(page) || swsusp_page_is_free(page))
@@ -1056,35 +1058,37 @@ copy_data_pages(struct memory_bitmap *co
#ifdef CONFIG_SNAPSHOT_VERIFICATION
struct page *d_page;
void *hash_buffer = NULL;
- struct crypto_shash *tfm;
- struct shash_desc *desc;
- u8 *digest;
+ struct crypto_shash *tfm = NULL;
+ struct shash_desc *desc = NULL;
+ u8 *digest = NULL;
size_t digest_size, desc_size;
struct key *s4_sign_key;
struct public_key_signature *pks;
int ret;
ret = -ENOMEM;
- tfm = crypto_alloc_shash(SNAPSHOT_HASH, 0, 0);
- if (IS_ERR(tfm)) {
- pr_err("IS_ERR(tfm): %ld", PTR_ERR(tfm));
- return PTR_ERR(tfm);
- }
-
- desc_size = crypto_shash_descsize(tfm) + sizeof(*desc);
- digest_size = crypto_shash_digestsize(tfm);
- digest = kzalloc(digest_size + desc_size, GFP_KERNEL);
- if (!digest) {
- pr_err("digest allocate fail");
- ret = -ENOMEM;
- goto error_digest;
- }
- desc = (void *) digest + digest_size;
- desc->tfm = tfm;
- desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
- ret = crypto_shash_init(desc);
- if (ret < 0)
- goto error_shash;
+ if (!capable(CAP_COMPROMISE_KERNEL)) {
+ tfm = crypto_alloc_shash(SNAPSHOT_HASH, 0, 0);
+ if (IS_ERR(tfm)) {
+ pr_err("IS_ERR(tfm): %ld", PTR_ERR(tfm));
+ return PTR_ERR(tfm);
+ }
+
+ desc_size = crypto_shash_descsize(tfm) + sizeof(*desc);
+ digest_size = crypto_shash_digestsize(tfm);
+ digest = kzalloc(digest_size + desc_size, GFP_KERNEL);
+ if (!digest) {
+ pr_err("digest allocate fail");
+ ret = -ENOMEM;
+ goto error_digest;
+ }
+ desc = (void *) digest + digest_size;
+ desc->tfm = tfm;
+ desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
+ ret = crypto_shash_init(desc);
+ if (ret < 0)
+ goto error_shash;
+ }
#endif /* CONFIG_SNAPSHOT_VERIFICATION */
for_each_populated_zone(zone) {
@@ -1106,24 +1110,29 @@ copy_data_pages(struct memory_bitmap *co
copy_data_page(dst_pfn, pfn);
#ifdef CONFIG_SNAPSHOT_VERIFICATION
- /* Generate digest */
- d_page = pfn_to_page(dst_pfn);
- if (PageHighMem(d_page)) {
- void *kaddr;
- kaddr = kmap_atomic(d_page);
- copy_page(buffer, kaddr);
- kunmap_atomic(kaddr);
- hash_buffer = buffer;
- } else {
- hash_buffer = page_address(d_page);
+ if (!capable(CAP_COMPROMISE_KERNEL)) {
+ /* Generate digest */
+ d_page = pfn_to_page(dst_pfn);
+ if (PageHighMem(d_page)) {
+ void *kaddr;
+ kaddr = kmap_atomic(d_page);
+ copy_page(buffer, kaddr);
+ kunmap_atomic(kaddr);
+ hash_buffer = buffer;
+ } else {
+ hash_buffer = page_address(d_page);
+ }
+ ret = crypto_shash_update(desc, hash_buffer, PAGE_SIZE);
+ if (ret)
+ goto error_shash;
}
- ret = crypto_shash_update(desc, hash_buffer, PAGE_SIZE);
- if (ret)
- goto error_shash;
#endif
}
#ifdef CONFIG_SNAPSHOT_VERIFICATION
+ if (capable(CAP_COMPROMISE_KERNEL))
+ goto skip_sign;
+
crypto_shash_final(desc, digest);
if (ret)
goto error_shash;
@@ -1153,6 +1162,8 @@ copy_data_pages(struct memory_bitmap *co
kfree(pks);
kfree(digest);
crypto_free_shash(tfm);
+
+skip_sign:
#endif /* CONFIG_SNAPSHOT_VERIFICATION */
return 0;
@@ -2362,9 +2373,11 @@ int snapshot_write_next(struct snapshot_
/* Allocate void * array to keep buffer point for generate hash,
* h_buf will freed in snapshot_image_verify().
*/
- h_buf = kmalloc(sizeof(void *) * nr_copy_pages, GFP_KERNEL);
- if (!h_buf)
- pr_err("Allocate hash buffer fail!");
+ if (!capable(CAP_COMPROMISE_KERNEL)) {
+ h_buf = kmalloc(sizeof(void *) * nr_copy_pages, GFP_KERNEL);
+ if (!h_buf)
+ pr_err("Allocate hash buffer fail!");
+ }
#endif
error = memory_bm_create(©_bm, GFP_ATOMIC, PG_ANY);
@@ -2394,7 +2407,7 @@ int snapshot_write_next(struct snapshot_
if (IS_ERR(handle->buffer))
return PTR_ERR(handle->buffer);
#ifdef CONFIG_SNAPSHOT_VERIFICATION
- if (h_buf)
+ if (!capable(CAP_COMPROMISE_KERNEL) && h_buf)
*h_buf = handle->buffer;
#endif
}
@@ -2408,7 +2421,7 @@ int snapshot_write_next(struct snapshot_
if (handle->buffer != buffer)
handle->sync_read = 0;
#ifdef CONFIG_SNAPSHOT_VERIFICATION
- if (h_buf)
+ if (!capable(CAP_COMPROMISE_KERNEL) && h_buf)
*(h_buf + (handle->cur - nr_meta_pages - 1)) = handle->buffer;
#endif
}
Index: linux/kernel/power/swap.c
===================================================================
--- linux.orig/kernel/power/swap.c
+++ linux/kernel/power/swap.c
@@ -1005,7 +1005,7 @@ static int load_image(struct swap_map_ha
if (!snapshot_image_loaded(snapshot))
ret = -ENODATA;
#ifdef CONFIG_SNAPSHOT_VERIFICATION
- else {
+ else if (!capable(CAP_COMPROMISE_KERNEL)) {
ret = snapshot_image_verify();
if (ret)
pr_info("PM: snapshot signature check FAIL: %d\n", ret);
@@ -1368,7 +1368,7 @@ out_finish:
}
}
#ifdef CONFIG_SNAPSHOT_VERIFICATION
- if (!ret) {
+ if (!ret && !capable(CAP_COMPROMISE_KERNEL)) {
ret = snapshot_image_verify();
if (ret)
pr_info("PM: snapshot signature check FAIL: %d\n", ret);
Index: linux/kernel/power/user.c
===================================================================
--- linux.orig/kernel/power/user.c
+++ linux/kernel/power/user.c
@@ -48,6 +48,14 @@ static int snapshot_open(struct inode *i
struct snapshot_data *data;
int error;
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+ if (!capable(CAP_COMPROMISE_KERNEL) && find_wake_key_data()) {
+#else
+ if (!capable(CAP_COMPROMISE_KERNEL)) {
+#endif
+ return -EPERM;
+ }
+
lock_system_sleep();
if (!atomic_add_unless(&snapshot_device_available, -1, 0)) {
@@ -258,6 +266,8 @@ static long snapshot_ioctl(struct file *
break;
}
#ifdef CONFIG_SNAPSHOT_VERIFICATION
+ if (capable(CAP_COMPROMISE_KERNEL))
+ goto skip_verify;
if (!snapshot_image_verify())
pr_info("PM: snapshot signature check SUCCESS!\n");
else {
@@ -265,6 +275,7 @@ static long snapshot_ioctl(struct file *
error = -EPERM;
break;
}
+skip_verify:
#endif
error = hibernation_restore(data->platform_support);
break;
--
To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org
To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org