@@ -14,6 +14,7 @@ struct resume_swap_area {
} __attribute__((packed));
#define USWSUSP_KEY_NONCE_SIZE 16
+#define USWSUSP_USER_KEY_SIZE 32
/*
* This structure is used to pass the kernel's hibernate encryption key in
@@ -22,9 +23,20 @@ struct resume_swap_area {
struct uswsusp_key_blob {
__u32 blob_len;
__u8 blob[512];
- __u8 nonce[USWSUSP_KEY_NONCE_SIZE];
+ __u8 nonce[USWSUSP_KEY_NONCE_SIZE] __nonstring;
} __attribute__((packed));
+/*
+ * Allow user mode to fold in key material for the data portion of the hibernate
+ * image.
+ */
+struct uswsusp_user_key {
+ /* Kernel returns the metadata size. */
+ __kernel_loff_t meta_size;
+ __u32 key_len;
+ __u8 key[USWSUSP_USER_KEY_SIZE] __nonstring;
+};
+
#define SNAPSHOT_IOC_MAGIC '3'
#define SNAPSHOT_FREEZE _IO(SNAPSHOT_IOC_MAGIC, 1)
#define SNAPSHOT_UNFREEZE _IO(SNAPSHOT_IOC_MAGIC, 2)
@@ -42,6 +54,7 @@ struct uswsusp_key_blob {
#define SNAPSHOT_AVAIL_SWAP_SIZE _IOR(SNAPSHOT_IOC_MAGIC, 19, __kernel_loff_t)
#define SNAPSHOT_ALLOC_SWAP_PAGE _IOR(SNAPSHOT_IOC_MAGIC, 20, __kernel_loff_t)
#define SNAPSHOT_ENABLE_ENCRYPTION _IOWR(SNAPSHOT_IOC_MAGIC, 21, struct uswsusp_key_blob)
-#define SNAPSHOT_IOC_MAXNR 21
+#define SNAPSHOT_SET_USER_KEY _IOWR(SNAPSHOT_IOC_MAGIC, 22, struct uswsusp_user_key)
+#define SNAPSHOT_IOC_MAXNR 22
#endif /* _LINUX_SUSPEND_IOCTLS_H */
@@ -98,6 +98,7 @@ config ENCRYPTED_HIBERNATION
depends on CRYPTO_AEAD2=y
depends on TCG_TPM2_RESTRICT_PCR
depends on TRUSTED_KEYS=y
+ select CRYPTO_LIB_SHA256
help
Enable support for kernel-based encryption of hibernation snapshots
created by uswsusp tools.
@@ -151,6 +151,7 @@ struct snapshot_handle {
extern unsigned int snapshot_additional_pages(struct zone *zone);
extern unsigned long snapshot_get_image_size(void);
+extern unsigned long snapshot_get_meta_page_count(void);
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);
@@ -6,6 +6,7 @@
#include <crypto/gcm.h>
#include <keys/trusted-type.h>
#include <linux/key-type.h>
+#include <crypto/sha.h>
#include <linux/random.h>
#include <linux/mm.h>
#include <linux/tpm.h>
@@ -21,6 +22,44 @@ static struct tpm_digest known_digest = { .alg_id = TPM_ALG_SHA256,
0xf1, 0x22, 0x38, 0x6c, 0x33, 0xb1, 0x14, 0xb7, 0xec, 0x05,
0x5f, 0x49}};
+/* Derive a key from the kernel and user keys for data encryption. */
+static int snapshot_use_user_key(struct snapshot_data *data)
+{
+ u8 digest[SHA256_DIGEST_SIZE];
+ struct trusted_key_payload *payload = data->key->payload.data[0];
+ struct sha256_state sha256_state;
+
+ /*
+ * Hash the kernel key and the user key together. This folds in the user
+ * key, but not in a way that gives the user mode predictable control
+ * over the key bits.
+ */
+ sha256_init(&sha256_state);
+
+ BUILD_BUG_ON(sizeof(payload->key) < SNAPSHOT_ENCRYPTION_KEY_SIZE);
+
+ sha256_update(&sha256_state, payload->key, SNAPSHOT_ENCRYPTION_KEY_SIZE);
+ sha256_update(&sha256_state, data->user_key, sizeof(data->user_key));
+ sha256_final(&sha256_state, digest);
+
+ BUILD_BUG_ON(SNAPSHOT_ENCRYPTION_KEY_SIZE > SHA256_DIGEST_SIZE);
+
+ return crypto_aead_setkey(data->aead_tfm,
+ digest,
+ SNAPSHOT_ENCRYPTION_KEY_SIZE);
+}
+
+/* Check to see if it's time to switch to the user key, and do it if so. */
+static int snapshot_check_user_key_switch(struct snapshot_data *data)
+{
+ if (data->user_key_valid && data->meta_size &&
+ data->crypt_total == data->meta_size) {
+ return snapshot_use_user_key(data);
+ }
+
+ return 0;
+}
+
/* Encrypt more data from the snapshot into the staging area. */
static int snapshot_encrypt_refill(struct snapshot_data *data)
{
@@ -31,6 +70,15 @@ static int snapshot_encrypt_refill(struct snapshot_data *data)
int pg_idx;
int res;
+ if (data->crypt_total == 0) {
+ data->meta_size = snapshot_get_meta_page_count() << PAGE_SHIFT;
+
+ } else {
+ res = snapshot_check_user_key_switch(data);
+ if (res)
+ return res;
+ }
+
/*
* The first buffer is the associated data, set to the offset to prevent
* attacks that rearrange chunks.
@@ -41,6 +89,11 @@ static int snapshot_encrypt_refill(struct snapshot_data *data)
for (pg_idx = 0; pg_idx < CHUNK_SIZE; pg_idx++) {
void *buf = data->crypt_pages[pg_idx];
+ /* Stop at the meta page boundary to potentially switch keys. */
+ if (total &&
+ ((data->crypt_total + total) == data->meta_size))
+ break;
+
res = snapshot_read_next(&data->handle);
if (res < 0)
return res;
@@ -113,10 +166,10 @@ static int snapshot_decrypt_drain(struct snapshot_data *data)
sg_set_buf(&data->sg[1 + pg_idx], data->crypt_pages[pg_idx], PAGE_SIZE);
/*
- * It's possible this is the final decrypt, and there are fewer than
- * CHUNK_SIZE pages. If this is the case we would have just written the
- * auth tag into the first few bytes of a new page. Copy to the tag if
- * so.
+ * It's possible this is the final decrypt, or the final decrypt of the
+ * meta region, and there are fewer than CHUNK_SIZE pages. If this is
+ * the case we would have just written the auth tag into the first few
+ * bytes of a new page. Copy to the tag if so.
*/
if ((page_count < CHUNK_SIZE) &&
(data->crypt_offset - total) == sizeof(data->auth_tag)) {
@@ -171,7 +224,14 @@ static int snapshot_decrypt_drain(struct snapshot_data *data)
total += PAGE_SIZE;
}
+ if (data->crypt_total == 0)
+ data->meta_size = snapshot_get_meta_page_count() << PAGE_SHIFT;
+
data->crypt_total += total;
+ res = snapshot_check_user_key_switch(data);
+ if (res)
+ return res;
+
return 0;
}
@@ -220,8 +280,26 @@ static ssize_t snapshot_write_next_encrypted(struct snapshot_data *data,
if (data->crypt_offset < (PAGE_SIZE * CHUNK_SIZE)) {
size_t pg_idx = data->crypt_offset >> PAGE_SHIFT;
size_t pg_off = data->crypt_offset & (PAGE_SIZE - 1);
+ size_t size_avail = PAGE_SIZE;
*buf = data->crypt_pages[pg_idx] + pg_off;
- return PAGE_SIZE - pg_off;
+
+ /*
+ * If this is the boundary where the meta pages end, then just
+ * return enough for the auth tag.
+ */
+ if (data->meta_size && (data->crypt_total < data->meta_size)) {
+ uint64_t total_done =
+ data->crypt_total + data->crypt_offset;
+
+ if ((total_done >= data->meta_size) &&
+ (total_done <
+ (data->meta_size + SNAPSHOT_AUTH_TAG_SIZE))) {
+
+ size_avail = SNAPSHOT_AUTH_TAG_SIZE;
+ }
+ }
+
+ return size_avail - pg_off;
}
/* Use offsets just beyond the size to return the tag. */
@@ -303,9 +381,15 @@ ssize_t snapshot_write_encrypted(struct snapshot_data *data,
break;
}
- /* Drain the encrypted buffer if it's full. */
+ /*
+ * Drain the encrypted buffer if it's full, or if we hit the end
+ * of the meta pages and need a key change.
+ */
if ((data->crypt_offset >=
- ((PAGE_SIZE * CHUNK_SIZE) + SNAPSHOT_AUTH_TAG_SIZE))) {
+ ((PAGE_SIZE * CHUNK_SIZE) + SNAPSHOT_AUTH_TAG_SIZE)) ||
+ (data->meta_size && (data->crypt_total < data->meta_size) &&
+ ((data->crypt_total + data->crypt_offset) ==
+ (data->meta_size + SNAPSHOT_AUTH_TAG_SIZE)))) {
int rc;
@@ -349,6 +433,8 @@ void snapshot_teardown_encryption(struct snapshot_data *data)
data->crypt_pages[i] = NULL;
}
}
+
+ memset(data->user_key, 0, sizeof(data->user_key));
}
static int snapshot_setup_encryption_common(struct snapshot_data *data)
@@ -358,6 +444,7 @@ static int snapshot_setup_encryption_common(struct snapshot_data *data)
data->crypt_total = 0;
data->crypt_offset = 0;
data->crypt_size = 0;
+ data->user_key_valid = false;
memset(data->crypt_pages, 0, sizeof(data->crypt_pages));
/* This only works once per hibernate. */
if (data->aead_tfm)
@@ -660,15 +747,74 @@ int snapshot_set_encryption_key(struct snapshot_data *data,
return rc;
}
-loff_t snapshot_get_encrypted_image_size(loff_t raw_size)
+static loff_t snapshot_encrypted_byte_count(loff_t plain_size)
{
- loff_t pages = raw_size >> PAGE_SHIFT;
+ loff_t pages = plain_size >> PAGE_SHIFT;
loff_t chunks = (pages + (CHUNK_SIZE - 1)) / CHUNK_SIZE;
/*
* The encrypted size is the normal size, plus a stitched in
* authentication tag for every chunk of pages.
*/
- return raw_size + (chunks * SNAPSHOT_AUTH_TAG_SIZE);
+ return plain_size + (chunks * SNAPSHOT_AUTH_TAG_SIZE);
+}
+
+static loff_t snapshot_get_meta_data_size(void)
+{
+ loff_t pages = snapshot_get_meta_page_count();
+
+ return snapshot_encrypted_byte_count(pages << PAGE_SHIFT);
+}
+
+int snapshot_set_user_key(struct snapshot_data *data,
+ struct uswsusp_user_key __user *key)
+{
+ struct uswsusp_user_key user_key;
+ unsigned int key_len;
+ int rc;
+ loff_t size;
+
+ /*
+ * Return the metadata size, the number of bytes that can be fed in before
+ * the user data key is needed at resume time.
+ */
+ size = snapshot_get_meta_data_size();
+ rc = put_user(size, &key->meta_size);
+ if (rc)
+ return rc;
+
+ rc = copy_from_user(&user_key, key, sizeof(struct uswsusp_user_key));
+ if (rc)
+ return rc;
+
+ BUILD_BUG_ON(sizeof(data->user_key) < sizeof(user_key.key));
+
+ key_len = min_t(__u32, user_key.key_len, sizeof(data->user_key));
+ if (key_len < 8)
+ return -EINVAL;
+
+ /* Don't allow it if it's too late. */
+ if (data->crypt_total > data->meta_size)
+ return -EBUSY;
+
+ memset(data->user_key, 0, sizeof(data->user_key));
+ memcpy(data->user_key, user_key.key, key_len);
+ data->user_key_valid = true;
+ /* Install the key if the user is just under the wire. */
+ rc = snapshot_check_user_key_switch(data);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+loff_t snapshot_get_encrypted_image_size(loff_t raw_size)
+{
+ loff_t pages = raw_size >> PAGE_SHIFT;
+ loff_t meta_size;
+
+ pages -= snapshot_get_meta_page_count();
+ meta_size = snapshot_get_meta_data_size();
+ return snapshot_encrypted_byte_count(pages << PAGE_SHIFT) + meta_size;
}
int snapshot_finalize_decrypted_image(struct snapshot_data *data)
@@ -2083,6 +2083,11 @@ unsigned long snapshot_get_image_size(void)
return nr_copy_pages + nr_meta_pages + 1;
}
+unsigned long snapshot_get_meta_page_count(void)
+{
+ return nr_meta_pages + 1;
+}
+
static int init_header(struct swsusp_info *info)
{
memset(info, 0, sizeof(struct swsusp_info));
@@ -427,6 +427,10 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
error = snapshot_set_encryption_key(data, (void __user *)arg);
break;
+ case SNAPSHOT_SET_USER_KEY:
+ error = snapshot_set_user_key(data, (void __user *)arg);
+ break;
+
default:
error = -ENOTTY;
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/crypto.h>
+#include <linux/suspend_ioctls.h>
#include <crypto/aead.h>
#include <crypto/aes.h>
@@ -32,6 +33,9 @@ struct snapshot_data {
uint64_t nonce_low;
uint64_t nonce_high;
struct key *key;
+ u8 user_key[USWSUSP_USER_KEY_SIZE] __nonstring;
+ bool user_key_valid;
+ uint64_t meta_size;
#endif
};
@@ -55,6 +59,9 @@ int snapshot_get_encryption_key(struct snapshot_data *data,
int snapshot_set_encryption_key(struct snapshot_data *data,
struct uswsusp_key_blob __user *key);
+int snapshot_set_user_key(struct snapshot_data *data,
+ struct uswsusp_user_key __user *key);
+
loff_t snapshot_get_encrypted_image_size(loff_t raw_size);
int snapshot_finalize_decrypted_image(struct snapshot_data *data);
@@ -89,6 +96,12 @@ static int snapshot_set_encryption_key(struct snapshot_data *data,
return -ENOTTY;
}
+static int snapshot_set_user_key(struct snapshot_data *data,
+ struct uswsusp_user_key __user *key)
+{
+ return -ENOTTY;
+}
+
static loff_t snapshot_get_encrypted_image_size(loff_t raw_size)
{
return raw_size;