This patch add the code for generate/verify signature of snapshot, it
put the signature to snapshot header. This approach can support both
on userspace hibernate and in-kernel hibernate.
Signed-off-by: Lee, Chun-Yi
---
kernel/power/power.h | 5 +
kernel/power/snapshot.c | 239 ++++++++++++++++++++++++++++++++++++++++++++++-
kernel/power/swap.c | 10 ++
kernel/power/user.c | 11 ++
4 files changed, 261 insertions(+), 4 deletions(-)
diff --git a/kernel/power/power.h b/kernel/power/power.h
index 03d7d45..56ef656 100644
--- a/kernel/power/power.h
+++ b/kernel/power/power.h
@@ -3,6 +3,9 @@
#include
#include
+/* The maximum length of snapshot signature */
+#define SIG_LENG 512
+
struct swsusp_info {
struct new_utsname uts;
u32 version_code;
@@ -11,6 +14,7 @@ struct swsusp_info {
unsigned long image_pages;
unsigned long pages;
unsigned long size;
+ u8 signature[SIG_LENG];
} __attribute__((aligned(PAGE_SIZE)));
#ifdef CONFIG_HIBERNATION
@@ -134,6 +138,7 @@ extern int snapshot_read_next(struct snapshot_handle *handle);
extern int snapshot_write_next(struct snapshot_handle *handle);
extern void snapshot_write_finalize(struct snapshot_handle *handle);
extern int snapshot_image_loaded(struct snapshot_handle *handle);
+extern int snapshot_image_verify(void);
/* If unset, the snapshot device cannot be open. */
extern atomic_t snapshot_device_available;
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index 349587b..7a817c2 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -27,6 +27,9 @@
#include
#include
#include
+#include
+#include
+#include
#include
#include
@@ -1031,11 +1034,49 @@ static inline void copy_data_page(unsigned long dst_pfn, unsigned long src_pfn)
}
#endif /* CONFIG_HIGHMEM */
-static void
+#define SNAPSHOT_HASH "sha256"
+
+/*
+ * Signature of snapshot for check.
+ */
+static u8 signature[SIG_LENG];
+
+static int
copy_data_pages(struct memory_bitmap *copy_bm, struct memory_bitmap *orig_bm)
{
struct zone *zone;
- unsigned long pfn;
+ unsigned long pfn, dst_pfn;
+ struct page *d_page;
+ void *hash_buffer = NULL;
+ struct crypto_shash *tfm;
+ struct shash_desc *desc;
+ u8 *digest;
+ 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;
for_each_populated_zone(zone) {
unsigned long max_zone_pfn;
@@ -1052,8 +1093,65 @@ copy_data_pages(struct memory_bitmap *copy_bm, struct memory_bitmap *orig_bm)
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);
+ 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;
+ 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;
+ }
+
+ crypto_shash_final(desc, digest);
+ if (ret)
+ goto error_shash;
+
+ /* Generate signature by private key */
+ s4_sign_key = get_sign_key();
+ if (!s4_sign_key || IS_ERR(s4_sign_key)) {
+ pr_err("Get S4 sign key fail: %ld\n", PTR_ERR(s4_sign_key));
+ ret = PTR_ERR(s4_sign_key);
+ goto error_key;
}
+
+ pks = generate_signature(s4_sign_key, digest, PKEY_HASH_SHA256, false);
+ if (IS_ERR(pks)) {
+ pr_err("Generate signature fail: %lx", PTR_ERR(pks));
+ ret = PTR_ERR(pks);
+ goto error_sign;
+ } else
+ memcpy(signature, pks->S, pks->k);
+
+ destroy_sign_key(s4_sign_key);
+
+ if (pks && pks->digest)
+ kfree(pks->digest);
+ if (pks && pks->rsa.s)
+ mpi_free(pks->rsa.s);
+ kfree(pks);
+ kfree(digest);
+ crypto_free_shash(tfm);
+
+ return 0;
+
+error_sign:
+ destroy_sign_key(s4_sign_key);
+error_key:
+error_shash:
+ kfree(digest);
+error_digest:
+ crypto_free_shash(tfm);
+ return ret;
}
/* Total number of image pages */
@@ -1580,6 +1678,7 @@ swsusp_alloc(struct memory_bitmap *orig_bm, struct memory_bitmap *copy_bm,
asmlinkage int swsusp_save(void)
{
unsigned int nr_pages, nr_highmem;
+ int ret;
printk(KERN_INFO "PM: Creating hibernation image:\n");
@@ -1602,7 +1701,9 @@ asmlinkage int swsusp_save(void)
* Kill them.
*/
drain_local_pages(NULL);
- copy_data_pages(©_bm, &orig_bm);
+ ret = copy_data_pages(©_bm, &orig_bm);
+ if (ret)
+ return ret;
/*
* End of critical section. From now on, we can write to memory,
@@ -1657,6 +1758,7 @@ static int init_header(struct swsusp_info *info)
info->pages = snapshot_get_image_size();
info->size = info->pages;
info->size <<= PAGE_SHIFT;
+ memcpy(info->signature, signature, SIG_LENG);
return init_header_complete(info);
}
@@ -1819,6 +1921,7 @@ load_header(struct swsusp_info *info)
if (!error) {
nr_copy_pages = info->image_pages;
nr_meta_pages = info->pages - info->image_pages - 1;
+ memcpy(signature, info->signature, SIG_LENG);
}
return error;
}
@@ -2194,6 +2297,8 @@ static void *get_buffer(struct memory_bitmap *bm, struct chain_allocator *ca)
return pbe->address;
}
+void **h_buf;
+
/**
* snapshot_write_next - used for writing the system memory snapshot.
*
@@ -2236,6 +2341,13 @@ int snapshot_write_next(struct snapshot_handle *handle)
if (error)
return error;
+ /* 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!");
+
error = memory_bm_create(©_bm, GFP_ATOMIC, PG_ANY);
if (error)
return error;
@@ -2262,6 +2374,8 @@ int snapshot_write_next(struct snapshot_handle *handle)
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();
@@ -2272,6 +2386,8 @@ int snapshot_write_next(struct snapshot_handle *handle)
return PTR_ERR(handle->buffer);
if (handle->buffer != buffer)
handle->sync_read = 0;
+ if (h_buf)
+ *(h_buf + (handle->cur - nr_meta_pages - 1)) = handle->buffer;
}
handle->cur++;
return PAGE_SIZE;
@@ -2304,6 +2420,121 @@ int snapshot_image_loaded(struct snapshot_handle *handle)
handle->cur <= nr_meta_pages + nr_copy_pages);
}
+int snapshot_verify_signature(u8 *digest, size_t digest_size)
+{
+ struct key *s4_wake_key;
+ struct public_key_signature *pks;
+ int ret;
+ MPI mpi;
+
+ /* load public key */
+ s4_wake_key = load_wake_key();
+ if (!s4_wake_key || IS_ERR(s4_wake_key)) {
+ pr_err("Get S4 wake key fail: %ld\n", PTR_ERR(s4_wake_key));
+ return PTR_ERR(s4_wake_key);
+ }
+
+ pks = kzalloc(digest_size + sizeof(*pks), GFP_KERNEL);
+ if (!pks) {
+ pr_err("Allocate public key signature fail!");
+ return -ENOMEM;
+ }
+ pks->pkey_hash_algo = PKEY_HASH_SHA256;
+ pks->digest = digest;
+ pks->digest_size = digest_size;
+
+ mpi = mpi_read_raw_data(signature, get_key_length(s4_wake_key));
+ if (!mpi) {
+ pr_err("PM: mpi_read_raw_data fail!\n");
+ ret = -ENOMEM;
+ goto error_mpi;
+ }
+ pks->mpi[0] = mpi;
+ pks->nr_mpi = 1;
+
+ /* RSA signature check */
+ ret = verify_signature(s4_wake_key, pks);
+ if (ret) {
+ pr_err("snapshot S4 signature verification fail: %d\n", ret);
+ goto error_verify;
+ } else
+ pr_info("snapshot S4 signature verification pass!\n");
+
+ if (pks->rsa.s)
+ mpi_free(pks->rsa.s);
+ kfree(pks);
+
+ return 0;
+
+error_verify:
+ if (pks->rsa.s)
+ mpi_free(pks->rsa.s);
+error_mpi:
+ kfree(pks);
+ return ret;
+}
+
+int snapshot_image_verify(void)
+{
+ struct crypto_shash *tfm;
+ struct shash_desc *desc;
+ u8 *digest;
+ size_t digest_size, desc_size;
+ int ret, i;
+
+ if (!h_buf)
+ return 0;
+
+ 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;
+
+ for (i = 0; i < nr_copy_pages; i++) {
+ ret = crypto_shash_update(desc, *(h_buf + i), PAGE_SIZE);
+ if (ret)
+ goto error_shash;
+ }
+
+ ret = crypto_shash_final(desc, digest);
+ if (ret)
+ goto error_shash;
+
+ ret = snapshot_verify_signature(digest, digest_size);
+ if (ret)
+ goto error_verify;
+
+ kfree(h_buf);
+ kfree(digest);
+ crypto_free_shash(tfm);
+ return 0;
+
+error_verify:
+error_shash:
+ kfree(h_buf);
+ kfree(digest);
+error_digest:
+ crypto_free_shash(tfm);
+ return ret;
+}
+
#ifdef CONFIG_HIGHMEM
/* Assumes that @buf is ready and points to a "safe" page */
static inline void
diff --git a/kernel/power/swap.c b/kernel/power/swap.c
index 7c33ed2..7dc54e6 100644
--- a/kernel/power/swap.c
+++ b/kernel/power/swap.c
@@ -1004,6 +1004,11 @@ static int load_image(struct swap_map_handle *handle,
snapshot_write_finalize(snapshot);
if (!snapshot_image_loaded(snapshot))
ret = -ENODATA;
+ ret = snapshot_image_verify();
+ if (ret)
+ pr_info("PM: snapshot signature check FAIL: %d\n", ret);
+ else
+ pr_info("PM: snapshot signature check SUCCESS!\n");
}
swsusp_show_speed(&start, &stop, nr_to_read, "Read");
return ret;
@@ -1358,6 +1363,11 @@ out_finish:
}
}
}
+ ret = snapshot_image_verify();
+ if (ret)
+ pr_info("PM: snapshot signature check FAIL: %d\n", ret);
+ else
+ pr_info("PM: snapshot signature check SUCCESS!\n");
}
swsusp_show_speed(&start, &stop, nr_to_read, "Read");
out_clean:
diff --git a/kernel/power/user.c b/kernel/power/user.c
index 4ed81e7..31bf500 100644
--- a/kernel/power/user.c
+++ b/kernel/power/user.c
@@ -196,6 +196,7 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
struct snapshot_data *data;
loff_t size;
sector_t offset;
+ int skey_error = 0;
if (_IOC_TYPE(cmd) != SNAPSHOT_IOC_MAGIC)
return -ENOTTY;
@@ -228,6 +229,9 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
if (!data->frozen || data->ready)
break;
pm_restore_gfp_mask();
+ skey_error = load_sign_key_data();
+ if (skey_error)
+ pr_err("Load private key fail: %d", skey_error);
thaw_processes();
data->frozen = 0;
break;
@@ -253,6 +257,13 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
error = -EPERM;
break;
}
+ if (!snapshot_image_verify())
+ pr_info("PM: snapshot signature check SUCCESS!\n");
+ else {
+ pr_info("PM: snapshot signature check FAIL!\n");
+ error = -EPERM;
+ break;
+ }
error = hibernation_restore(data->platform_support);
break;
--
1.6.4.2
--
To unsubscribe, e-mail: opensuse-kernel+unsubscribe@opensuse.org
To contact the owner, e-mail: opensuse-kernel+owner@opensuse.org