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.

v2:
Replace sign_key_data_loaded() by skey_data_available() to check sign key data
is available for hibernate.

Reviewed-by: Jiri Kosina <jkos...@suse.cz>
Signed-off-by: Lee, Chun-Yi <j...@suse.com>
---
 kernel/power/hibernate.c |   36 +++++++++++++++++-
 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(+), 45 deletions(-)

diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c
index c545b15..0f19f3d 100644
--- a/kernel/power/hibernate.c
+++ b/kernel/power/hibernate.c
@@ -29,6 +29,7 @@
 #include <linux/ctype.h>
 #include <linux/genhd.h>
 #include <linux/key.h>
+#include <linux/efi.h>
 
 #include "power.h"
 
@@ -632,7 +633,14 @@ static void power_down(void)
 int hibernate(void)
 {
        int error;
-       int skey_error;
+
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+       if (!capable(CAP_COMPROMISE_KERNEL) && !skey_data_available()) {
+#else
+       if (!capable(CAP_COMPROMISE_KERNEL)) {
+#endif
+               return -EPERM;
+       }
 
        lock_system_sleep();
        /* The snapshot device should not be opened while we're running */
@@ -799,6 +807,15 @@ static int software_resume(void)
        if (error)
                goto Unlock;
 
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+       if (!capable(CAP_COMPROMISE_KERNEL) && !wkey_data_available()) {
+#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;
@@ -892,6 +909,15 @@ static ssize_t disk_show(struct kobject *kobj, struct 
kobj_attribute *attr,
        int i;
        char *start = buf;
 
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+       if (efi_enabled(EFI_SECURE_BOOT) && !skey_data_available()) {
+#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;
@@ -926,6 +952,14 @@ static ssize_t disk_store(struct kobject *kobj, struct 
kobj_attribute *attr,
        char *p;
        int mode = HIBERNATION_INVALID;
 
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+       if (!capable(CAP_COMPROMISE_KERNEL) && !skey_data_available()) {
+#else
+       if (!capable(CAP_COMPROMISE_KERNEL)) {
+#endif
+               return -EPERM;
+       }
+
        p = memchr(buf, '\n', n);
        len = p ? p - buf : n;
 
diff --git a/kernel/power/main.c b/kernel/power/main.c
index 1d1bf63..47bf300 100644
--- a/kernel/power/main.c
+++ b/kernel/power/main.c
@@ -15,6 +15,7 @@
 #include <linux/workqueue.h>
 #include <linux/debugfs.h>
 #include <linux/seq_file.h>
+#include <linux/efi.h>
 
 #include "power.h"
 
@@ -301,7 +302,15 @@ static ssize_t state_show(struct kobject *kobj, struct 
kobj_attribute *attr,
        }
 #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 (skey_data_available()) {
+               s += sprintf(s, "%s\n", "disk");
+#endif
+       } else {
+               s += sprintf(s, "\n");
+       }
 #else
        if (s != buf)
                /* convert the last space to a newline */
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index cf3d69c..36c7157 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -860,7 +860,8 @@ static struct page *saveable_highmem_page(struct zone 
*zone, unsigned long pfn)
 
        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 zone *zone, 
unsigned long pfn)
 
        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 *copy_bm, struct 
memory_bitmap *orig_bm)
 #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);
-       }
+       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_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;
        }
-       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 *copy_bm, struct 
memory_bitmap *orig_bm)
                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 *copy_bm, struct 
memory_bitmap *orig_bm)
        kfree(pks);
        kfree(digest);
        crypto_free_shash(tfm);
+
+skip_sign:
 #endif /* CONFIG_SNAPSHOT_VERIFICATION */
 
        return 0;
@@ -2382,9 +2393,11 @@ int snapshot_write_next(struct snapshot_handle *handle)
                /* 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(&copy_bm, GFP_ATOMIC, PG_ANY);
@@ -2414,7 +2427,7 @@ int snapshot_write_next(struct snapshot_handle *handle)
                        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
                }
@@ -2428,7 +2441,7 @@ int snapshot_write_next(struct snapshot_handle *handle)
                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;
                /* Keep the buffer of sign key in snapshot */
                if (pfn == skey_data_buf_pfn)
diff --git a/kernel/power/swap.c b/kernel/power/swap.c
index b5f8ce1..40225d7 100644
--- a/kernel/power/swap.c
+++ b/kernel/power/swap.c
@@ -1005,7 +1005,7 @@ static int load_image(struct swap_map_handle *handle,
                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);
@@ -1370,7 +1370,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);
diff --git a/kernel/power/user.c b/kernel/power/user.c
index 27b21ee..690f148 100644
--- a/kernel/power/user.c
+++ b/kernel/power/user.c
@@ -48,6 +48,14 @@ static int snapshot_open(struct inode *inode, struct file 
*filp)
        struct snapshot_data *data;
        int error;
 
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+       if (!capable(CAP_COMPROMISE_KERNEL) && !wkey_data_available()) {
+#else
+       if (!capable(CAP_COMPROMISE_KERNEL)) {
+#endif
+               return -EPERM;
+       }
+
        lock_system_sleep();
 
        if (!atomic_add_unless(&snapshot_device_available, -1, 0)) {
@@ -255,6 +263,8 @@ static long snapshot_ioctl(struct file *filp, unsigned int 
cmd,
                        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");
                        snapshot_fill_s4_skey();
@@ -263,6 +273,7 @@ static long snapshot_ioctl(struct file *filp, unsigned int 
cmd,
                        error = -EPERM;
                        break;
                }
+skip_verify:
 #endif
                error = hibernation_restore(data->platform_support);
                break;
-- 
1.6.4.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to