[PATCH 6/9] pm: hibernate: Optionally store and verify a hash of the image

From: Matthew Garrett
Date: Fri Feb 19 2021 - 20:35:47 EST


Calculate and store a cryptographically secure hash of the hibernation
image if SF_VERIFY_IMAGE is set in the hibernation flags. This allows
detection of a corrupt image, but has the disadvantage that it requires
the blocks be read in in linear order.

Signed-off-by: Matthew Garrett <mjg59@xxxxxxxxxx>
---
kernel/power/power.h | 1 +
kernel/power/swap.c | 131 +++++++++++++++++++++++++++++++++++--------
2 files changed, 110 insertions(+), 22 deletions(-)

diff --git a/kernel/power/power.h b/kernel/power/power.h
index 778bf431ec02..b8e00b9dcee8 100644
--- a/kernel/power/power.h
+++ b/kernel/power/power.h
@@ -168,6 +168,7 @@ extern int swsusp_swap_in_use(void);
#define SF_PLATFORM_MODE 1
#define SF_NOCOMPRESS_MODE 2
#define SF_CRC32_MODE 4
+#define SF_VERIFY_IMAGE 8

/* kernel/power/hibernate.c */
extern int swsusp_check(void);
diff --git a/kernel/power/swap.c b/kernel/power/swap.c
index 72e33054a2e1..a13241a20567 100644
--- a/kernel/power/swap.c
+++ b/kernel/power/swap.c
@@ -31,6 +31,8 @@
#include <linux/kthread.h>
#include <linux/crc32.h>
#include <linux/ktime.h>
+#include <crypto/hash.h>
+#include <crypto/sha2.h>

#include "power.h"

@@ -95,17 +97,20 @@ struct swap_map_page_list {
struct swap_map_handle {
struct swap_map_page *cur;
struct swap_map_page_list *maps;
+ struct shash_desc *desc;
sector_t cur_swap;
sector_t first_sector;
unsigned int k;
unsigned long reqd_free_pages;
u32 crc32;
+ u8 digest[SHA256_DIGEST_SIZE];
};

struct swsusp_header {
char reserved[PAGE_SIZE - 20 - sizeof(sector_t) - sizeof(int) -
- sizeof(u32)];
+ sizeof(u32) - SHA256_DIGEST_SIZE];
u32 crc32;
+ u8 digest[SHA256_DIGEST_SIZE];
sector_t image;
unsigned int flags; /* Flags to pass to the "boot" kernel */
char orig_sig[10];
@@ -305,6 +310,9 @@ static blk_status_t hib_wait_io(struct hib_bio_batch *hb)
* We are relying on the behavior of blk_plug that a thread with
* a plug will flush the plug list before sleeping.
*/
+ if (!hb)
+ return 0;
+
wait_event(hb->wait, atomic_read(&hb->count) == 0);
return blk_status_to_errno(hb->error);
}
@@ -327,6 +335,8 @@ static int mark_swapfiles(struct swap_map_handle *handle, unsigned int flags)
swsusp_header->flags = flags;
if (flags & SF_CRC32_MODE)
swsusp_header->crc32 = handle->crc32;
+ memcpy(swsusp_header->digest, handle->digest,
+ SHA256_DIGEST_SIZE);
error = hib_submit_io(REQ_OP_WRITE, REQ_SYNC,
swsusp_resume_block, swsusp_header, NULL);
} else {
@@ -417,6 +427,7 @@ static void release_swap_writer(struct swap_map_handle *handle)
static int get_swap_writer(struct swap_map_handle *handle)
{
int ret;
+ struct crypto_shash *tfm;

ret = swsusp_swap_check();
if (ret) {
@@ -437,7 +448,28 @@ static int get_swap_writer(struct swap_map_handle *handle)
handle->k = 0;
handle->reqd_free_pages = reqd_free_pages();
handle->first_sector = handle->cur_swap;
+
+ tfm = crypto_alloc_shash("sha256", 0, 0);
+ if (IS_ERR(tfm)) {
+ ret = -EINVAL;
+ goto err_rel;
+ }
+ handle->desc = kmalloc(sizeof(struct shash_desc) +
+ crypto_shash_descsize(tfm), GFP_KERNEL);
+ if (!handle->desc) {
+ ret = -ENOMEM;
+ goto err_rel;
+ }
+
+ handle->desc->tfm = tfm;
+
+ ret = crypto_shash_init(handle->desc);
+ if (ret != 0)
+ goto err_free;
+
return 0;
+err_free:
+ kfree(handle->desc);
err_rel:
release_swap_writer(handle);
err_close:
@@ -446,7 +478,7 @@ static int get_swap_writer(struct swap_map_handle *handle)
}

static int swap_write_page(struct swap_map_handle *handle, void *buf,
- struct hib_bio_batch *hb)
+ struct hib_bio_batch *hb, bool hash)
{
int error = 0;
sector_t offset;
@@ -454,6 +486,7 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf,
if (!handle->cur)
return -EINVAL;
offset = alloc_swapdev_block(root_swap);
+ crypto_shash_update(handle->desc, buf, PAGE_SIZE);
error = write_page(buf, offset, hb);
if (error)
return error;
@@ -496,6 +529,7 @@ static int flush_swap_writer(struct swap_map_handle *handle)
static int swap_writer_finish(struct swap_map_handle *handle,
unsigned int flags, int error)
{
+ crypto_shash_final(handle->desc, handle->digest);
if (!error) {
pr_info("S");
error = mark_swapfiles(handle, flags);
@@ -560,7 +594,7 @@ static int save_image(struct swap_map_handle *handle,
ret = snapshot_read_next(snapshot);
if (ret <= 0)
break;
- ret = swap_write_page(handle, data_of(*snapshot), &hb);
+ ret = swap_write_page(handle, data_of(*snapshot), &hb, true);
if (ret)
break;
if (!(nr_pages % m))
@@ -844,7 +878,7 @@ static int save_image_lzo(struct swap_map_handle *handle,
off += PAGE_SIZE) {
memcpy(page, data[thr].cmp + off, PAGE_SIZE);

- ret = swap_write_page(handle, page, &hb);
+ ret = swap_write_page(handle, page, &hb, true);
if (ret)
goto out_finish;
}
@@ -938,7 +972,7 @@ int swsusp_write(unsigned int flags)
goto out_finish;
}
header = (struct swsusp_info *)data_of(snapshot);
- error = swap_write_page(&handle, header, NULL);
+ error = swap_write_page(&handle, header, NULL, false);
if (!error) {
error = (flags & SF_NOCOMPRESS_MODE) ?
save_image(&handle, &snapshot, pages - 1) :
@@ -974,6 +1008,7 @@ static int get_swap_reader(struct swap_map_handle *handle,
int error;
struct swap_map_page_list *tmp, *last;
sector_t offset;
+ struct crypto_shash *tfm;

*flags_p = swsusp_header->flags;

@@ -1011,11 +1046,34 @@ static int get_swap_reader(struct swap_map_handle *handle,
}
handle->k = 0;
handle->cur = handle->maps->map;
+
+ tfm = crypto_alloc_shash("sha256", 0, 0);
+ if (IS_ERR(tfm)) {
+ error = -EINVAL;
+ goto err_rel;
+ }
+ handle->desc = kmalloc(sizeof(struct shash_desc) +
+ crypto_shash_descsize(tfm), GFP_KERNEL);
+ if (!handle->desc) {
+ error = -ENOMEM;
+ goto err_rel;
+ }
+
+ handle->desc->tfm = tfm;
+
+ error = crypto_shash_init(handle->desc);
+ if (error != 0)
+ goto err_free;
return 0;
+err_free:
+ kfree(handle->desc);
+err_rel:
+ release_swap_reader(handle);
+ return error;
}

static int swap_read_page(struct swap_map_handle *handle, void *buf,
- struct hib_bio_batch *hb)
+ struct hib_bio_batch *hb, bool hash)
{
sector_t offset;
int error;
@@ -1029,6 +1087,7 @@ static int swap_read_page(struct swap_map_handle *handle, void *buf,
error = hib_submit_io(REQ_OP_READ, 0, offset, buf, hb);
if (error)
return error;
+ crypto_shash_update(handle->desc, buf, PAGE_SIZE);
if (++handle->k >= MAP_PAGE_ENTRIES) {
handle->k = 0;
free_page((unsigned long)handle->maps->map);
@@ -1043,11 +1102,21 @@ static int swap_read_page(struct swap_map_handle *handle, void *buf,
return error;
}

-static int swap_reader_finish(struct swap_map_handle *handle)
+static int swap_reader_finish(struct swap_map_handle *handle,
+ struct swsusp_info *header)
{
+ int ret = 0;
+
+ crypto_shash_final(handle->desc, handle->digest);
+ if (memcmp(handle->digest, swsusp_header->digest,
+ SHA256_DIGEST_SIZE) != 0) {
+ pr_err("Image digest doesn't match header digest\n");
+ ret = -ENODATA;
+ }
+
release_swap_reader(handle);

- return 0;
+ return ret;
}

/**
@@ -1064,11 +1133,20 @@ static int load_image(struct swap_map_handle *handle,
int ret = 0;
ktime_t start;
ktime_t stop;
- struct hib_bio_batch hb;
+ struct hib_bio_batch *hb, real_hb;
int err2;
unsigned nr_pages;

- hib_init_batch(&hb);
+ /*
+ * If we're calculating the SHA256 of the image, we need the blocks
+ * to be read in in order
+ */
+ if (swsusp_header->flags & SF_VERIFY_IMAGE) {
+ hb = NULL;
+ } else {
+ hib_init_batch(&real_hb);
+ hb = &real_hb;
+ }

clean_pages_on_read = true;
pr_info("Loading image data pages (%u pages)...\n", nr_to_read);
@@ -1081,11 +1159,11 @@ static int load_image(struct swap_map_handle *handle,
ret = snapshot_write_next(snapshot);
if (ret <= 0)
break;
- ret = swap_read_page(handle, data_of(*snapshot), &hb);
+ ret = swap_read_page(handle, data_of(*snapshot), hb, true);
if (ret)
break;
if (snapshot->sync_read)
- ret = hib_wait_io(&hb);
+ ret = hib_wait_io(hb);
if (ret)
break;
if (!(nr_pages % m))
@@ -1093,8 +1171,8 @@ static int load_image(struct swap_map_handle *handle,
nr_pages / m * 10);
nr_pages++;
}
- err2 = hib_wait_io(&hb);
- hib_finish_batch(&hb);
+ err2 = hib_wait_io(hb);
+ hib_finish_batch(hb);
stop = ktime_get();
if (!ret)
ret = err2;
@@ -1169,7 +1247,7 @@ static int load_image_lzo(struct swap_map_handle *handle,
unsigned int m;
int ret = 0;
int eof = 0;
- struct hib_bio_batch hb;
+ struct hib_bio_batch *hb, real_hb;
ktime_t start;
ktime_t stop;
unsigned nr_pages;
@@ -1182,7 +1260,16 @@ static int load_image_lzo(struct swap_map_handle *handle,
struct dec_data *data = NULL;
struct crc_data *crc = NULL;

- hib_init_batch(&hb);
+ /*
+ * If we're calculating the SHA256 of the image, we need the blocks
+ * to be read in in order
+ */
+ if (swsusp_header->flags & SF_VERIFY_IMAGE) {
+ hb = NULL;
+ } else {
+ hib_init_batch(&real_hb);
+ hb = &real_hb;
+ }

/*
* We'll limit the number of threads for decompression to limit memory
@@ -1301,7 +1388,7 @@ static int load_image_lzo(struct swap_map_handle *handle,

for(;;) {
for (i = 0; !eof && i < want; i++) {
- ret = swap_read_page(handle, page[ring], &hb);
+ ret = swap_read_page(handle, page[ring], hb, true);
if (ret) {
/*
* On real read error, finish. On end of data,
@@ -1328,7 +1415,7 @@ static int load_image_lzo(struct swap_map_handle *handle,
if (!asked)
break;

- ret = hib_wait_io(&hb);
+ ret = hib_wait_io(hb);
if (ret)
goto out_finish;
have += asked;
@@ -1382,7 +1469,7 @@ static int load_image_lzo(struct swap_map_handle *handle,
* Wait for more data while we are decompressing.
*/
if (have < LZO_CMP_PAGES && asked) {
- ret = hib_wait_io(&hb);
+ ret = hib_wait_io(hb);
if (ret)
goto out_finish;
have += asked;
@@ -1458,7 +1545,7 @@ static int load_image_lzo(struct swap_map_handle *handle,
}
swsusp_show_speed(start, stop, nr_to_read, "Read");
out_clean:
- hib_finish_batch(&hb);
+ hib_finish_batch(hb);
for (i = 0; i < ring_size; i++)
free_page((unsigned long)page[i]);
if (crc) {
@@ -1499,13 +1586,13 @@ int swsusp_read(unsigned int *flags_p)
if (error)
goto end;
if (!error)
- error = swap_read_page(&handle, header, NULL);
+ error = swap_read_page(&handle, header, NULL, false);
if (!error) {
error = (*flags_p & SF_NOCOMPRESS_MODE) ?
load_image(&handle, &snapshot, header->pages - 1) :
load_image_lzo(&handle, &snapshot, header->pages - 1);
}
- swap_reader_finish(&handle);
+ error = swap_reader_finish(&handle, header);
end:
if (!error)
pr_debug("Image successfully loaded\n");
--
2.30.0.617.g56c4b15f3c-goog