| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/04 12:12 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"ENCRYPTED_HIBERNATION",
"HIBERNATION_SNAPSHOT_DEV",
"HIBERNATION"
],
"FocusSymbols": [
"snapshot_store_encryption_seed",
"snapshot_get_encryption_key",
"snapshot_set_encryption_key",
"snapshot_set_user_key",
"snapshot_read_encrypted",
"snapshot_write_encrypted",
"snapshot_finalize_decrypted_image",
"hibernate_snapshot_write_begin",
"hibernate_snapshot_write_end",
"swsusp_swap_range_allocated",
"swapdev_block_to_extent"
],
"Reasoning": "The patch introduces kernel-based encryption for userspace hibernation snapshots. It adds new ioctls to `/dev/snapshot` (`SNAPSHOT_ENABLE_ENCRYPTION`, `SNAPSHOT_SET_USER_KEY`), a sysfs interface `/sys/power/snapshot_seed`, and modifies the read/write paths for the snapshot device to support encryption/decryption. It also hooks into block device writes to track swap allocations. These are reachable core kernel changes that should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/04 12:12 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit a03c020e7a2c2fff2ea2b4d38087c084d66457aa\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Aug 4 12:12:54 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power\nindex d38da077905a9..57f6063ef88c9 100644\n--- a/Documentation/ABI/testing/sysfs-power\n+++ b/Documentation/ABI/testing/sysfs-power\n@@ -470,3 +470,17 @@ Description:\n \n Minimum value: 1\n Default value: 3\n+\n+What:\t\t/sys/power/snapshot_seed\n+Date:\t\tJuly 2026\n+Contact:\tlinux-pm@vger.kernel.org\n+Description:\n+\t\tWrite-only file present when CONFIG_ENCRYPTED_HIBERNATION=y.\n+\n+\t\tTrusted early userspace writes a 32-byte seed, encoded as\n+\t\t64 hexadecimal characters plus an optional newline, before\n+\t\tenabling encrypted userspace hibernation snapshots.\n+\n+\t\tThe first successful write locks the seed until the next boot.\n+\t\tWriting the same seed again succeeds. Writing a different seed\n+\t\tfails with EPERM.\ndiff --git a/Documentation/power/userland-swsusp.rst b/Documentation/power/userland-swsusp.rst\nindex 1cf62d80a9ca1..df8eca9e6e363 100644\n--- a/Documentation/power/userland-swsusp.rst\n+++ b/Documentation/power/userland-swsusp.rst\n@@ -115,6 +115,26 @@ SNAPSHOT_S2RAM\n \tto resume the system from RAM if there's enough battery power or restore\n \tits state on the basis of the saved suspend image otherwise)\n \n+SNAPSHOT_ENABLE_ENCRYPTION\n+\tEnables encryption of the hibernate image within the kernel. Trusted\n+\tearly userspace must write the 32-byte snapshot encryption seed to\n+\t/sys/power/snapshot_seed before calling this ioctl. Upon suspend\n+\t(ie when the snapshot device was opened for reading), this ioctl returns\n+\tan opaque wrapped key blob and the starting nonce. Upon resume (ie when\n+\tthe snapshot device was opened for writing), this ioctl receives the\n+\tsame blob and nonce from usermode after the same seed has been written\n+\tto /sys/power/snapshot_seed. The plaintext image key is generated and\n+\tunwrapped inside the kernel.\n+\n+SNAPSHOT_SET_USER_KEY\n+\tMixes additional user key material into the data portion of an encrypted\n+\thibernate image. The ioctl argument points to struct uswsusp_user_key.\n+\tkey_len must be between 8 and USWSUSP_USER_KEY_SIZE bytes, and reserved\n+\tmust be zero. During hibernation the kernel writes meta_size with the\n+\tencrypted metadata size. During resume, userspace passes the saved\n+\tmeta_size back so the kernel can switch keys at the metadata boundary\n+\tbefore the snapshot header has been decrypted.\n+\n The device's read() operation can be used to transfer the snapshot image from\n the kernel. It has the following limitations:\n \ndiff --git a/block/fops.c b/block/fops.c\nindex 15783a6180dec..efeb485e91e6c 100644\n--- a/block/fops.c\n+++ b/block/fops.c\n@@ -723,43 +723,65 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)\n \tstruct file *file = iocb-\u003eki_filp;\n \tstruct inode *bd_inode = bdev_file_inode(file);\n \tstruct block_device *bdev = I_BDEV(bd_inode);\n+\tdev_t dev = bd_inode-\u003ei_rdev;\n \tbool atomic = iocb-\u003eki_flags \u0026 IOCB_ATOMIC;\n \tloff_t size = bdev_nr_bytes(bdev);\n+\tenum hibernate_snapshot_write hibernate_write;\n+\tsize_t hibernate_len = 0;\n \tsize_t shorted = 0;\n+\tssize_t hibernate_written = 0;\n \tssize_t ret;\n \n \tif (bdev_read_only(bdev))\n \t\treturn -EPERM;\n \n-\tif (IS_SWAPFILE(bd_inode) \u0026\u0026 !is_hibernate_resume_dev(bd_inode-\u003ei_rdev))\n-\t\treturn -ETXTBSY;\n+\tif (IS_SWAPFILE(bd_inode) \u0026\u0026 !is_hibernate_resume_dev(dev)) {\n+\t\tif (!is_sync_kiocb(iocb) || !iocb_is_dsync(iocb) ||\n+\t\t (iocb-\u003eki_flags \u0026 IOCB_DIRECT))\n+\t\t\treturn -ETXTBSY;\n \n-\tif (!iov_iter_count(from))\n-\t\treturn 0;\n+\t\thibernate_len = iov_iter_count(from);\n+\t\thibernate_write = hibernate_snapshot_write_begin(dev, iocb-\u003eki_pos, hibernate_len);\n+\t\tif (hibernate_write == HIBERNATE_SNAPSHOT_WRITE_NONE)\n+\t\t\treturn -ETXTBSY;\n+\t} else {\n+\t\thibernate_write = HIBERNATE_SNAPSHOT_WRITE_NONE;\n+\t}\n \n-\tif (iocb-\u003eki_pos \u003e= size)\n-\t\treturn -ENOSPC;\n+\tif (!iov_iter_count(from)) {\n+\t\tret = 0;\n+\t\tgoto out_hibernate;\n+\t}\n \n-\tif ((iocb-\u003eki_flags \u0026 (IOCB_NOWAIT | IOCB_DIRECT)) == IOCB_NOWAIT)\n-\t\treturn -EOPNOTSUPP;\n+\tif (iocb-\u003eki_pos \u003e= size) {\n+\t\tret = -ENOSPC;\n+\t\tgoto out_hibernate;\n+\t}\n+\n+\tif ((iocb-\u003eki_flags \u0026 (IOCB_NOWAIT | IOCB_DIRECT)) == IOCB_NOWAIT) {\n+\t\tret = -EOPNOTSUPP;\n+\t\tgoto out_hibernate;\n+\t}\n \n \tif (atomic) {\n \t\tret = generic_atomic_write_valid(iocb, from);\n \t\tif (ret)\n-\t\t\treturn ret;\n+\t\t\tgoto out_hibernate;\n \t}\n \n \tsize -= iocb-\u003eki_pos;\n \tif (iov_iter_count(from) \u003e size) {\n-\t\tif (atomic)\n-\t\t\treturn -EINVAL;\n+\t\tif (atomic) {\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto out_hibernate;\n+\t\t}\n \t\tshorted = iov_iter_count(from) - size;\n \t\tiov_iter_truncate(from, size);\n \t}\n \n \tret = file_update_time(file);\n \tif (ret)\n-\t\treturn ret;\n+\t\tgoto out_hibernate;\n \n \tif (iocb-\u003eki_flags \u0026 IOCB_DIRECT) {\n \t\tret = blkdev_direct_write(iocb, from);\n@@ -777,9 +799,16 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)\n \t\tinode_unlock_shared(bd_inode);\n \t}\n \n-\tif (ret \u003e 0)\n+\tif (ret \u003e 0) {\n+\t\thibernate_written = ret;\n \t\tret = generic_write_sync(iocb, ret);\n+\t}\n \tiov_iter_reexpand(from, iov_iter_count(from) + shorted);\n+\n+out_hibernate:\n+\tif (hibernate_len)\n+\t\thibernate_snapshot_write_end(hibernate_write, hibernate_len,\n+\t\t\t\t\t hibernate_written);\n \treturn ret;\n }\n \ndiff --git a/include/linux/suspend.h b/include/linux/suspend.h\nindex b02876f1ae38a..8308a6583756c 100644\n--- a/include/linux/suspend.h\n+++ b/include/linux/suspend.h\n@@ -426,10 +426,32 @@ static inline bool pm_hibernation_mode_is_suspend(void) { return false; }\n \n int arch_resume_nosmt(void);\n \n+enum hibernate_snapshot_write {\n+\tHIBERNATE_SNAPSHOT_WRITE_NONE,\n+\tHIBERNATE_SNAPSHOT_WRITE_IMAGE,\n+\tHIBERNATE_SNAPSHOT_WRITE_HEADER,\n+};\n+\n #ifdef CONFIG_HIBERNATION_SNAPSHOT_DEV\n int is_hibernate_resume_dev(dev_t dev);\n+enum hibernate_snapshot_write\n+hibernate_snapshot_write_begin(dev_t dev, loff_t pos, size_t count);\n+void hibernate_snapshot_write_end(enum hibernate_snapshot_write type,\n+\t\t\t\t size_t reserved, ssize_t written);\n #else\n static inline int is_hibernate_resume_dev(dev_t dev) { return 0; }\n+\n+static inline enum hibernate_snapshot_write\n+hibernate_snapshot_write_begin(dev_t dev, loff_t pos, size_t count)\n+{\n+\treturn HIBERNATE_SNAPSHOT_WRITE_NONE;\n+}\n+\n+static inline void hibernate_snapshot_write_end(enum hibernate_snapshot_write type,\n+\t\t\t\t\t\tsize_t reserved,\n+\t\t\t\t\t\tssize_t written)\n+{\n+}\n #endif\n \n /* Hibernation and suspend events */\ndiff --git a/include/linux/swap.h b/include/linux/swap.h\nindex 8f0f68e245baa..b36685d1f314b 100644\n--- a/include/linux/swap.h\n+++ b/include/linux/swap.h\n@@ -439,6 +439,9 @@ extern int find_hibernation_swap_type(dev_t device, sector_t offset);\n int find_first_swap(dev_t *device);\n extern unsigned int count_swap_pages(int, int);\n extern sector_t swapdev_block(int, pgoff_t);\n+int swapdev_block_to_offset(int type, sector_t block, pgoff_t *offset);\n+int swapdev_block_to_extent(int type, sector_t block, pgoff_t *offset,\n+\t\t\t pgoff_t *nr_pages);\n extern int __swap_count(swp_entry_t entry);\n extern bool swap_entry_swapped(struct swap_info_struct *si, swp_entry_t entry);\n extern int swp_swapcount(swp_entry_t entry);\n@@ -535,6 +538,19 @@ static inline int add_swap_extent(struct swap_info_struct *sis,\n {\n \treturn -EINVAL;\n }\n+\n+static inline int swapdev_block_to_offset(int type, sector_t block,\n+\t\t\t\t\t pgoff_t *offset)\n+{\n+\treturn -EINVAL;\n+}\n+\n+static inline int swapdev_block_to_extent(int type, sector_t block,\n+\t\t\t\t\t pgoff_t *offset,\n+\t\t\t\t\t pgoff_t *nr_pages)\n+{\n+\treturn -EINVAL;\n+}\n #endif /* CONFIG_SWAP */\n #ifdef CONFIG_MEMCG\n static inline int mem_cgroup_swappiness(struct mem_cgroup *memcg)\ndiff --git a/include/uapi/linux/suspend_ioctls.h b/include/uapi/linux/suspend_ioctls.h\nindex bcce04e21c0dc..5e23bf936277e 100644\n--- a/include/uapi/linux/suspend_ioctls.h\n+++ b/include/uapi/linux/suspend_ioctls.h\n@@ -13,6 +13,31 @@ struct resume_swap_area {\n \t__u32 dev;\n } __attribute__((packed));\n \n+#define USWSUSP_KEY_NONCE_SIZE 16\n+#define USWSUSP_USER_KEY_SIZE 32\n+\n+/*\n+ * This structure is used to pass the opaque wrapped hibernate image\n+ * encryption key and starting nonce in either direction.\n+ */\n+struct uswsusp_key_blob {\n+\t__u32 blob_len;\n+\t__u8 blob[512];\n+\t__u8 nonce[USWSUSP_KEY_NONCE_SIZE] __kernel_nonstring;\n+} __attribute__((packed));\n+\n+/*\n+ * Allow user mode to fold in key material for the data portion of the hibernate\n+ * image.\n+ */\n+struct uswsusp_user_key {\n+\t/* Kernel returns the metadata size; resume passes the saved value in. */\n+\t__u64 meta_size;\n+\t__u32 key_len;\n+\t__u32 reserved;\n+\t__u8 key[USWSUSP_USER_KEY_SIZE] __kernel_nonstring;\n+} __attribute__((packed));\n+\n #define SNAPSHOT_IOC_MAGIC\t'3'\n #define SNAPSHOT_FREEZE\t\t\t_IO(SNAPSHOT_IOC_MAGIC, 1)\n #define SNAPSHOT_UNFREEZE\t\t_IO(SNAPSHOT_IOC_MAGIC, 2)\n@@ -29,6 +54,10 @@ struct resume_swap_area {\n #define SNAPSHOT_PREF_IMAGE_SIZE\t_IO(SNAPSHOT_IOC_MAGIC, 18)\n #define SNAPSHOT_AVAIL_SWAP_SIZE\t_IOR(SNAPSHOT_IOC_MAGIC, 19, __kernel_loff_t)\n #define SNAPSHOT_ALLOC_SWAP_PAGE\t_IOR(SNAPSHOT_IOC_MAGIC, 20, __kernel_loff_t)\n-#define SNAPSHOT_IOC_MAXNR\t20\n+#define SNAPSHOT_ENABLE_ENCRYPTION\t_IOWR(SNAPSHOT_IOC_MAGIC, 21, \\\n+\t\t\t\t\t\t\tstruct uswsusp_key_blob)\n+#define SNAPSHOT_SET_USER_KEY\t\t_IOWR(SNAPSHOT_IOC_MAGIC, 22, \\\n+\t\t\t\t\t\t\tstruct uswsusp_user_key)\n+#define SNAPSHOT_IOC_MAXNR\t22\n \n #endif /* _LINUX_SUSPEND_IOCTLS_H */\ndiff --git a/kernel/power/Kconfig b/kernel/power/Kconfig\nindex 530c897311d44..a9bee71571cfa 100644\n--- a/kernel/power/Kconfig\n+++ b/kernel/power/Kconfig\n@@ -115,6 +115,21 @@ config HIBERNATION_DEF_COMP\n \thelp\n \t Default compressor to be used for hibernation.\n \n+config ENCRYPTED_HIBERNATION\n+\tbool \"Encryption support for userspace snapshots\"\n+\tdepends on HIBERNATION_SNAPSHOT_DEV\n+\tselect CRYPTO_AES\n+\tselect CRYPTO_GCM\n+\tselect CRYPTO_LIB_SHA256\n+\thelp\n+\t Enable support for kernel-based encryption of hibernation snapshots\n+\t created by uswsusp tools. A trusted early userspace component must\n+\t provide the snapshot encryption seed before enabling encryption.\n+\n+\t Say N if userspace handles the image encryption.\n+\n+\t If in doubt, say N.\n+\n config PM_STD_PARTITION\n \tstring \"Default resume partition\"\n \tdepends on HIBERNATION\ndiff --git a/kernel/power/Makefile b/kernel/power/Makefile\nindex 773e2789412b9..2fd31b2a250f5 100644\n--- a/kernel/power/Makefile\n+++ b/kernel/power/Makefile\n@@ -16,6 +16,7 @@ obj-$(CONFIG_SUSPEND)\t\t+= suspend.o\n obj-$(CONFIG_PM_TEST_SUSPEND)\t+= suspend_test.o\n obj-$(CONFIG_HIBERNATION)\t+= hibernate.o snapshot.o swap.o\n obj-$(CONFIG_HIBERNATION_SNAPSHOT_DEV) += user.o\n+obj-$(CONFIG_ENCRYPTED_HIBERNATION) += snapenc.o\n obj-$(CONFIG_PM_AUTOSLEEP)\t+= autosleep.o\n obj-$(CONFIG_PM_WAKELOCKS)\t+= wakelock.o\n \ndiff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c\nindex d2479c69d71a4..0b8626bdf81a8 100644\n--- a/kernel/power/hibernate.c\n+++ b/kernel/power/hibernate.c\n@@ -36,6 +36,7 @@\n #include \u003ctrace/events/power.h\u003e\n \n #include \"power.h\"\n+#include \"user.h\"\n \n \n static int nocompress;\n@@ -113,6 +114,14 @@ bool hibernation_available(void)\n \t\t!secretmem_active() \u0026\u0026 !cxl_mem_active();\n }\n \n+bool hibernation_snapshot_dev_available(void)\n+{\n+\treturn nohibernate == 0 \u0026\u0026\n+\t\t(!security_locked_down(LOCKDOWN_HIBERNATION) ||\n+\t\t IS_ENABLED(CONFIG_ENCRYPTED_HIBERNATION)) \u0026\u0026\n+\t\t!secretmem_active() \u0026\u0026 !cxl_mem_active();\n+}\n+\n /**\n * hibernation_set_ops - Set the global hibernate operations.\n * @ops: Hibernation operations to use in subsequent hibernation transitions.\n@@ -1376,12 +1385,35 @@ static ssize_t reserved_size_store(struct kobject *kobj,\n \n power_attr(reserved_size);\n \n+#ifdef CONFIG_ENCRYPTED_HIBERNATION\n+static ssize_t snapshot_seed_store(struct kobject *kobj,\n+\t\t\t\t struct kobj_attribute *attr,\n+\t\t\t\t const char *buf, size_t n)\n+{\n+\tint ret;\n+\n+\tret = snapshot_store_encryption_seed(buf, n);\n+\treturn ret ? ret : n;\n+}\n+\n+static struct kobj_attribute snapshot_seed_attr = {\n+\t.attr\t= {\n+\t\t.name = \"snapshot_seed\",\n+\t\t.mode = 0200,\n+\t},\n+\t.store\t= snapshot_seed_store,\n+};\n+#endif\n+\n static struct attribute *g[] = {\n \t\u0026disk_attr.attr,\n \t\u0026resume_offset_attr.attr,\n \t\u0026resume_attr.attr,\n \t\u0026image_size_attr.attr,\n \t\u0026reserved_size_attr.attr,\n+#ifdef CONFIG_ENCRYPTED_HIBERNATION\n+\t\u0026snapshot_seed_attr.attr,\n+#endif\n \tNULL,\n };\n \ndiff --git a/kernel/power/power.h b/kernel/power/power.h\nindex 75b63843886ee..39ef04be283dd 100644\n--- a/kernel/power/power.h\n+++ b/kernel/power/power.h\n@@ -160,6 +160,7 @@ struct snapshot_handle {\n \n extern unsigned int snapshot_additional_pages(struct zone *zone);\n extern unsigned long snapshot_get_image_size(void);\n+unsigned long snapshot_get_meta_page_count(void);\n extern int snapshot_read_next(struct snapshot_handle *handle);\n extern int snapshot_write_next(struct snapshot_handle *handle);\n int snapshot_write_finalize(struct snapshot_handle *handle);\n@@ -167,8 +168,11 @@ extern int snapshot_image_loaded(struct snapshot_handle *handle);\n \n extern bool hibernate_acquire(void);\n extern void hibernate_release(void);\n+bool hibernation_snapshot_dev_available(void);\n \n extern sector_t alloc_swapdev_block(int swap);\n+bool swsusp_swap_range_allocated(int swap, loff_t pos, size_t count);\n+u64 swsusp_swap_map_bytes(u64 image_bytes);\n extern void free_all_swap_pages(int swap);\n extern int swsusp_swap_in_use(void);\n \ndiff --git a/kernel/power/snapenc.c b/kernel/power/snapenc.c\nnew file mode 100644\nindex 0000000000000..cd29803ec6c5a\n--- /dev/null\n+++ b/kernel/power/snapenc.c\n@@ -0,0 +1,940 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/* This file provides encryption support for system snapshots. */\n+\n+#include \u003clinux/crypto.h\u003e\n+#include \u003ccrypto/aead.h\u003e\n+#include \u003ccrypto/gcm.h\u003e\n+#include \u003ccrypto/sha2.h\u003e\n+#include \u003ccrypto/utils.h\u003e\n+#include \u003clinux/hex.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/random.h\u003e\n+#include \u003clinux/mm.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/string.h\u003e\n+#include \u003clinux/uaccess.h\u003e\n+\n+#include \"power.h\"\n+#include \"user.h\"\n+\n+#define SNAPSHOT_SEED_SIZE SHA256_DIGEST_SIZE\n+#define SNAPSHOT_KEY_BLOB_VERSION 1\n+\n+static const u8 snapshot_key_blob_magic[8] = {\n+\t'S', 'W', 'S', 'U', 'S', 'P', 'K', '1',\n+};\n+\n+struct snapshot_wrapped_key {\n+\tu8 magic[sizeof(snapshot_key_blob_magic)];\n+\t__le32 version;\n+\tu8 wrap_nonce[GCM_AES_IV_SIZE] __nonstring;\n+\tu8 encrypted_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring;\n+\tu8 tag[SNAPSHOT_AUTH_TAG_SIZE] __nonstring;\n+} __packed;\n+\n+static DEFINE_MUTEX(snapshot_seed_mutex);\n+static u8 snapshot_seed[SNAPSHOT_SEED_SIZE] __nonstring;\n+static bool snapshot_seed_valid;\n+\n+int snapshot_store_encryption_seed(const char *buf, size_t count)\n+{\n+\tu8 seed[SNAPSHOT_SEED_SIZE] __nonstring;\n+\tsize_t len = count;\n+\tint ret;\n+\n+\tif (len \u0026\u0026 buf[len - 1] == '\\n')\n+\t\tlen--;\n+\tif (len != SNAPSHOT_SEED_SIZE * 2)\n+\t\treturn -EINVAL;\n+\n+\tret = hex2bin(seed, buf, sizeof(seed));\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tmutex_lock(\u0026snapshot_seed_mutex);\n+\tif (snapshot_seed_valid) {\n+\t\tret = crypto_memneq(snapshot_seed, seed, sizeof(seed)) ? -EPERM : 0;\n+\t\tgoto out;\n+\t}\n+\n+\tmemcpy(snapshot_seed, seed, sizeof(seed));\n+\tsnapshot_seed_valid = true;\n+\tpr_info(\"PM: hibernate: snapshot encryption seed locked\\n\");\n+\n+out:\n+\tmutex_unlock(\u0026snapshot_seed_mutex);\n+\tmemzero_explicit(seed, sizeof(seed));\n+\treturn ret;\n+}\n+\n+static bool snapshot_copy_encryption_seed(u8 seed[SNAPSHOT_SEED_SIZE])\n+{\n+\tbool valid;\n+\n+\tmutex_lock(\u0026snapshot_seed_mutex);\n+\tvalid = snapshot_seed_valid;\n+\tif (valid)\n+\t\tmemcpy(seed, snapshot_seed, SNAPSHOT_SEED_SIZE);\n+\tmutex_unlock(\u0026snapshot_seed_mutex);\n+\n+\treturn valid;\n+}\n+\n+static int snapshot_derive_wrapping_key(u8 key[SNAPSHOT_ENCRYPTION_KEY_SIZE])\n+{\n+\tstatic const char label[] = \"Linux hibernate snapshot key wrap v1\";\n+\tu8 digest[SHA256_DIGEST_SIZE];\n+\tu8 seed[SNAPSHOT_SEED_SIZE] __nonstring;\n+\tstruct sha256_ctx sha256_ctx;\n+\n+\tif (!snapshot_copy_encryption_seed(seed))\n+\t\treturn -ENOKEY;\n+\n+\tsha256_init(\u0026sha256_ctx);\n+\tsha256_update(\u0026sha256_ctx, label, strlen(label));\n+\tsha256_update(\u0026sha256_ctx, seed, sizeof(seed));\n+\tsha256_final(\u0026sha256_ctx, digest);\n+\n+\tmemcpy(key, digest, SNAPSHOT_ENCRYPTION_KEY_SIZE);\n+\tmemzero_explicit(seed, sizeof(seed));\n+\tmemzero_explicit(digest, sizeof(digest));\n+\treturn 0;\n+}\n+\n+static int snapshot_crypt_wrapped_key(struct snapshot_wrapped_key *wrapped,\n+\t\t\t\t u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE],\n+\t\t\t\t bool encrypt)\n+{\n+\tu8 wrap_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring;\n+\tu8 buf[SNAPSHOT_ENCRYPTION_KEY_SIZE + SNAPSHOT_AUTH_TAG_SIZE] __nonstring;\n+\tstruct crypto_aead *tfm;\n+\tstruct aead_request *req;\n+\tstruct scatterlist sg;\n+\tDECLARE_CRYPTO_WAIT(wait);\n+\tint rc;\n+\n+\trc = snapshot_derive_wrapping_key(wrap_key);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\ttfm = crypto_alloc_aead(\"gcm(aes)\", 0, 0);\n+\tif (IS_ERR(tfm)) {\n+\t\trc = PTR_ERR(tfm);\n+\t\tgoto out_key;\n+\t}\n+\n+\trc = crypto_aead_setkey(tfm, wrap_key, sizeof(wrap_key));\n+\tif (rc)\n+\t\tgoto out_tfm;\n+\n+\trc = crypto_aead_setauthsize(tfm, SNAPSHOT_AUTH_TAG_SIZE);\n+\tif (rc)\n+\t\tgoto out_tfm;\n+\n+\treq = aead_request_alloc(tfm, GFP_KERNEL);\n+\tif (!req) {\n+\t\trc = -ENOMEM;\n+\t\tgoto out_tfm;\n+\t}\n+\n+\tif (encrypt) {\n+\t\tget_random_bytes(wrapped-\u003ewrap_nonce, sizeof(wrapped-\u003ewrap_nonce));\n+\t\tmemcpy(buf, image_key, SNAPSHOT_ENCRYPTION_KEY_SIZE);\n+\t} else {\n+\t\tmemcpy(buf, wrapped-\u003eencrypted_key, SNAPSHOT_ENCRYPTION_KEY_SIZE);\n+\t\tmemcpy(buf + SNAPSHOT_ENCRYPTION_KEY_SIZE, wrapped-\u003etag,\n+\t\t SNAPSHOT_AUTH_TAG_SIZE);\n+\t}\n+\n+\tsg_init_one(\u0026sg, buf, sizeof(buf));\n+\taead_request_set_callback(req, 0, crypto_req_done, \u0026wait);\n+\taead_request_set_ad(req, 0);\n+\taead_request_set_crypt(req, \u0026sg, \u0026sg,\n+\t\t\t encrypt ? SNAPSHOT_ENCRYPTION_KEY_SIZE :\n+\t\t\t\t\t sizeof(buf),\n+\t\t\t wrapped-\u003ewrap_nonce);\n+\n+\trc = crypto_wait_req(encrypt ? crypto_aead_encrypt(req) :\n+\t\t\t\t crypto_aead_decrypt(req),\n+\t\t\t \u0026wait);\n+\tif (rc)\n+\t\tgoto out_req;\n+\n+\tif (encrypt) {\n+\t\tmemcpy(wrapped-\u003eencrypted_key, buf, SNAPSHOT_ENCRYPTION_KEY_SIZE);\n+\t\tmemcpy(wrapped-\u003etag, buf + SNAPSHOT_ENCRYPTION_KEY_SIZE,\n+\t\t SNAPSHOT_AUTH_TAG_SIZE);\n+\t} else {\n+\t\tmemcpy(image_key, buf, SNAPSHOT_ENCRYPTION_KEY_SIZE);\n+\t}\n+\n+out_req:\n+\taead_request_free(req);\n+out_tfm:\n+\tcrypto_free_aead(tfm);\n+out_key:\n+\tmemzero_explicit(buf, sizeof(buf));\n+\tmemzero_explicit(wrap_key, sizeof(wrap_key));\n+\treturn rc;\n+}\n+\n+static int snapshot_wrap_image_key(const u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE],\n+\t\t\t\t struct snapshot_wrapped_key *wrapped)\n+{\n+\tu8 key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring;\n+\tint rc;\n+\n+\tmemset(wrapped, 0, sizeof(*wrapped));\n+\tmemcpy(wrapped-\u003emagic, snapshot_key_blob_magic, sizeof(wrapped-\u003emagic));\n+\twrapped-\u003eversion = cpu_to_le32(SNAPSHOT_KEY_BLOB_VERSION);\n+\n+\tmemcpy(key, image_key, sizeof(key));\n+\trc = snapshot_crypt_wrapped_key(wrapped, key, true);\n+\tmemzero_explicit(key, sizeof(key));\n+\treturn rc;\n+}\n+\n+static int snapshot_unwrap_image_key(const struct snapshot_wrapped_key *wrapped,\n+\t\t\t\t u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE])\n+{\n+\tstruct snapshot_wrapped_key tmp;\n+\tint rc;\n+\n+\tif (memcmp(wrapped-\u003emagic, snapshot_key_blob_magic,\n+\t\t sizeof(snapshot_key_blob_magic)))\n+\t\treturn -EINVAL;\n+\tif (le32_to_cpu(wrapped-\u003eversion) != SNAPSHOT_KEY_BLOB_VERSION)\n+\t\treturn -EINVAL;\n+\n+\ttmp = *wrapped;\n+\trc = snapshot_crypt_wrapped_key(\u0026tmp, image_key, false);\n+\tmemzero_explicit(\u0026tmp, sizeof(tmp));\n+\treturn rc;\n+}\n+\n+static int snapshot_install_image_key(struct snapshot_data *data,\n+\t\t\t\t const u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE])\n+{\n+\tint rc;\n+\n+\tmemcpy(data-\u003eencryption_key, image_key, sizeof(data-\u003eencryption_key));\n+\trc = crypto_aead_setkey(data-\u003eaead_tfm, data-\u003eencryption_key,\n+\t\t\t\tsizeof(data-\u003eencryption_key));\n+\tif (rc)\n+\t\tmemzero_explicit(data-\u003eencryption_key,\n+\t\t\t\t sizeof(data-\u003eencryption_key));\n+\n+\treturn rc;\n+}\n+\n+/* Derive a key from the kernel and user keys for data encryption. */\n+static int snapshot_use_user_key(struct snapshot_data *data)\n+{\n+\tu8 digest[SHA256_DIGEST_SIZE];\n+\tstruct sha256_ctx sha256_ctx;\n+\tint rc;\n+\n+\t/*\n+\t * Hash the kernel key and the user key together. This folds in the user\n+\t * key, but not in a way that gives the user mode predictable control\n+\t * over the key bits.\n+\t */\n+\tsha256_init(\u0026sha256_ctx);\n+\n+\tsha256_update(\u0026sha256_ctx, data-\u003eencryption_key,\n+\t\t SNAPSHOT_ENCRYPTION_KEY_SIZE);\n+\tsha256_update(\u0026sha256_ctx, data-\u003euser_key, sizeof(data-\u003euser_key));\n+\tsha256_final(\u0026sha256_ctx, digest);\n+\n+\tBUILD_BUG_ON(SNAPSHOT_ENCRYPTION_KEY_SIZE \u003e SHA256_DIGEST_SIZE);\n+\n+\trc = crypto_aead_setkey(data-\u003eaead_tfm,\n+\t\t\t\tdigest,\n+\t\t\t\tSNAPSHOT_ENCRYPTION_KEY_SIZE);\n+\tmemzero_explicit(digest, sizeof(digest));\n+\n+\treturn rc;\n+}\n+\n+/* Check to see if it's time to switch to the user key, and do it if so. */\n+static int snapshot_check_user_key_switch(struct snapshot_data *data)\n+{\n+\tif (data-\u003euser_key_valid \u0026\u0026 data-\u003emeta_size \u0026\u0026\n+\t data-\u003ecrypt_total == data-\u003emeta_size) {\n+\t\treturn snapshot_use_user_key(data);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static loff_t snapshot_encrypted_byte_count(loff_t plain_size);\n+\n+static void snapshot_set_meta_size(struct snapshot_data *data, u64 meta_size)\n+{\n+\tdata-\u003emeta_size = meta_size;\n+\tdata-\u003ecrypt_meta_size = snapshot_encrypted_byte_count(meta_size);\n+}\n+\n+static void snapshot_record_encrypted_read(struct snapshot_data *data,\n+\t\t\t\t\t size_t count)\n+{\n+\tspin_lock(\u0026data-\u003ecrypt_lock);\n+\tdata-\u003ecrypt_bytes_read += count;\n+\tspin_unlock(\u0026data-\u003ecrypt_lock);\n+}\n+\n+/* Encrypt more data from the snapshot into the staging area. */\n+static int snapshot_encrypt_refill(struct snapshot_data *data)\n+{\n+\tstruct aead_request *req = data-\u003eaead_req;\n+\tu8 nonce[GCM_AES_IV_SIZE];\n+\tDECLARE_CRYPTO_WAIT(wait);\n+\tsize_t total = 0;\n+\tint pg_idx;\n+\tint res;\n+\n+\tif (data-\u003ecrypt_total == 0) {\n+\t\tsnapshot_set_meta_size(data,\n+\t\t\t\t snapshot_get_meta_page_count() \u003c\u003c PAGE_SHIFT);\n+\t} else {\n+\t\tres = snapshot_check_user_key_switch(data);\n+\t\tif (res)\n+\t\t\treturn res;\n+\t}\n+\n+\t/*\n+\t * The first buffer is the associated data, set to the offset to prevent\n+\t * attacks that rearrange chunks.\n+\t */\n+\tsg_set_buf(\u0026data-\u003esg[0], \u0026data-\u003ecrypt_total, sizeof(data-\u003ecrypt_total));\n+\n+\t/* Load the crypt buffer with snapshot pages. */\n+\tfor (pg_idx = 0; pg_idx \u003c CHUNK_SIZE; pg_idx++) {\n+\t\tvoid *buf = data-\u003ecrypt_pages[pg_idx];\n+\n+\t\t/* Stop at the meta page boundary before switching keys. */\n+\t\tif (data-\u003euser_key_valid \u0026\u0026 total \u0026\u0026\n+\t\t ((data-\u003ecrypt_total + total) == data-\u003emeta_size))\n+\t\t\tbreak;\n+\n+\t\tres = snapshot_read_next(\u0026data-\u003ehandle);\n+\t\tif (res \u003c 0)\n+\t\t\treturn res;\n+\t\tif (res == 0)\n+\t\t\tbreak;\n+\n+\t\tWARN_ON(res != PAGE_SIZE);\n+\n+\t\t/*\n+\t\t * Copy the page into the staging area. A future optimization\n+\t\t * could potentially skip this copy for lowmem pages.\n+\t\t */\n+\t\tmemcpy(buf, data_of(data-\u003ehandle), PAGE_SIZE);\n+\t\tsg_set_buf(\u0026data-\u003esg[1 + pg_idx], buf, PAGE_SIZE);\n+\t\ttotal += PAGE_SIZE;\n+\t}\n+\n+\tif (!total)\n+\t\treturn 0;\n+\n+\tsg_set_buf(\u0026data-\u003esg[1 + pg_idx], \u0026data-\u003eauth_tag, SNAPSHOT_AUTH_TAG_SIZE);\n+\taead_request_set_callback(req, 0, crypto_req_done, \u0026wait);\n+\t/*\n+\t * Use incrementing nonces for each chunk, since a 64 bit value won't\n+\t * roll into re-use for any given hibernate image.\n+\t */\n+\tmemcpy(\u0026nonce[0], \u0026data-\u003enonce_low, sizeof(data-\u003enonce_low));\n+\tmemcpy(\u0026nonce[sizeof(data-\u003enonce_low)],\n+\t \u0026data-\u003enonce_high,\n+\t sizeof(nonce) - sizeof(data-\u003enonce_low));\n+\n+\tdata-\u003enonce_low += 1;\n+\t/* Total does not include AAD or the auth tag. */\n+\taead_request_set_crypt(req, data-\u003esg, data-\u003esg, total, nonce);\n+\tres = crypto_wait_req(crypto_aead_encrypt(req), \u0026wait);\n+\tif (res)\n+\t\treturn res;\n+\n+\tdata-\u003ecrypt_size = total;\n+\tdata-\u003ecrypt_total += total;\n+\treturn 0;\n+}\n+\n+/* Decrypt data from the staging area and push it to the snapshot. */\n+static int snapshot_decrypt_drain(struct snapshot_data *data)\n+{\n+\tstruct aead_request *req = data-\u003eaead_req;\n+\tu8 nonce[GCM_AES_IV_SIZE];\n+\tDECLARE_CRYPTO_WAIT(wait);\n+\tint page_count;\n+\tsize_t total;\n+\tint pg_idx;\n+\tint res;\n+\n+\t/* Set up the associated data. */\n+\tsg_set_buf(\u0026data-\u003esg[0], \u0026data-\u003ecrypt_total, sizeof(data-\u003ecrypt_total));\n+\n+\t/*\n+\t * Get the number of full pages, which could be short at the end. There\n+\t * should also be a tag at the end, so the offset won't be an even page.\n+\t */\n+\tpage_count = data-\u003ecrypt_offset \u003e\u003e PAGE_SHIFT;\n+\ttotal = page_count \u003c\u003c PAGE_SHIFT;\n+\tif (total == 0 || total == data-\u003ecrypt_offset)\n+\t\treturn -EINVAL;\n+\n+\t/*\n+\t * Load the sg list with the crypt buffer. Inline decrypt back into the\n+\t * staging buffer. A future optimization could decrypt directly into\n+\t * lowmem pages.\n+\t */\n+\tfor (pg_idx = 0; pg_idx \u003c page_count; pg_idx++)\n+\t\tsg_set_buf(\u0026data-\u003esg[1 + pg_idx], data-\u003ecrypt_pages[pg_idx], PAGE_SIZE);\n+\n+\t/*\n+\t * It's possible this is the final decrypt, or the final decrypt of the\n+\t * meta region, and there are fewer than CHUNK_SIZE pages. If this is\n+\t * the case we would have just written the auth tag into the first few\n+\t * bytes of a new page. Copy to the tag if so.\n+\t */\n+\tif (page_count \u003c CHUNK_SIZE \u0026\u0026\n+\t (data-\u003ecrypt_offset - total) == sizeof(data-\u003eauth_tag)) {\n+\t\tmemcpy(data-\u003eauth_tag, data-\u003ecrypt_pages[pg_idx],\n+\t\t sizeof(data-\u003eauth_tag));\n+\t} else if (data-\u003ecrypt_offset !=\n+\t\t ((CHUNK_SIZE \u003c\u003c PAGE_SHIFT) + SNAPSHOT_AUTH_TAG_SIZE)) {\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tsg_set_buf(\u0026data-\u003esg[1 + pg_idx], \u0026data-\u003eauth_tag, SNAPSHOT_AUTH_TAG_SIZE);\n+\taead_request_set_callback(req, 0, crypto_req_done, \u0026wait);\n+\tmemcpy(\u0026nonce[0], \u0026data-\u003enonce_low, sizeof(data-\u003enonce_low));\n+\tmemcpy(\u0026nonce[sizeof(data-\u003enonce_low)],\n+\t \u0026data-\u003enonce_high,\n+\t sizeof(nonce) - sizeof(data-\u003enonce_low));\n+\n+\tdata-\u003enonce_low += 1;\n+\taead_request_set_crypt(req, data-\u003esg, data-\u003esg, total + SNAPSHOT_AUTH_TAG_SIZE, nonce);\n+\tres = crypto_wait_req(crypto_aead_decrypt(req), \u0026wait);\n+\tif (res)\n+\t\treturn res;\n+\n+\tdata-\u003ecrypt_size = 0;\n+\tdata-\u003ecrypt_offset = 0;\n+\n+\t/* Push the decrypted pages further down the stack. */\n+\ttotal = 0;\n+\tfor (pg_idx = 0; pg_idx \u003c page_count; pg_idx++) {\n+\t\tvoid *buf = data-\u003ecrypt_pages[pg_idx];\n+\n+\t\tres = snapshot_write_next(\u0026data-\u003ehandle);\n+\t\tif (res \u003c 0)\n+\t\t\treturn res;\n+\t\tif (res == 0)\n+\t\t\tbreak;\n+\n+\t\tif (!data_of(data-\u003ehandle))\n+\t\t\treturn -EINVAL;\n+\n+\t\tWARN_ON(res != PAGE_SIZE);\n+\n+\t\t/* Copy the decrypted page into the snapshot image. */\n+\t\tmemcpy(data_of(data-\u003ehandle), buf, PAGE_SIZE);\n+\t\ttotal += PAGE_SIZE;\n+\t}\n+\n+\tif (data-\u003ecrypt_total == 0) {\n+\t\tdata-\u003emeta_size = snapshot_get_meta_page_count() \u003c\u003c PAGE_SHIFT;\n+\t\tif (data-\u003euser_key_valid)\n+\t\t\tdata-\u003ecrypt_meta_size =\n+\t\t\t\tsnapshot_encrypted_byte_count(data-\u003emeta_size);\n+\t}\n+\n+\tdata-\u003ecrypt_total += total;\n+\tres = snapshot_check_user_key_switch(data);\n+\tif (res)\n+\t\treturn res;\n+\n+\treturn 0;\n+}\n+\n+static ssize_t snapshot_read_next_encrypted(struct snapshot_data *data,\n+\t\t\t\t\t void **buf)\n+{\n+\tsize_t tag_off;\n+\n+\t/* Refill the encrypted buffer if it's empty. */\n+\tif (data-\u003ecrypt_size == 0 ||\n+\t (data-\u003ecrypt_offset \u003e=\n+\t (data-\u003ecrypt_size + SNAPSHOT_AUTH_TAG_SIZE))) {\n+\t\tint rc;\n+\n+\t\tdata-\u003ecrypt_size = 0;\n+\t\tdata-\u003ecrypt_offset = 0;\n+\t\trc = snapshot_encrypt_refill(data);\n+\t\tif (rc \u003c 0)\n+\t\t\treturn rc;\n+\t\tif (!data-\u003ecrypt_size)\n+\t\t\treturn 0;\n+\t}\n+\n+\t/* Return data pages if the offset is in that region. */\n+\tif (data-\u003ecrypt_offset \u003c data-\u003ecrypt_size) {\n+\t\tsize_t pg_idx = data-\u003ecrypt_offset \u003e\u003e PAGE_SHIFT;\n+\t\tsize_t pg_off = data-\u003ecrypt_offset \u0026 (PAGE_SIZE - 1);\n+\t\t*buf = data-\u003ecrypt_pages[pg_idx] + pg_off;\n+\t\treturn PAGE_SIZE - pg_off;\n+\t}\n+\n+\t/* Use offsets just beyond the size to return the tag. */\n+\ttag_off = data-\u003ecrypt_offset - data-\u003ecrypt_size;\n+\tif (tag_off \u003e SNAPSHOT_AUTH_TAG_SIZE)\n+\t\ttag_off = SNAPSHOT_AUTH_TAG_SIZE;\n+\n+\t*buf = data-\u003eauth_tag + tag_off;\n+\treturn SNAPSHOT_AUTH_TAG_SIZE - tag_off;\n+}\n+\n+static ssize_t snapshot_write_next_encrypted(struct snapshot_data *data,\n+\t\t\t\t\t void **buf)\n+{\n+\tsize_t size_avail;\n+\tsize_t tag_off;\n+\n+\t/* Return data pages if the offset is in that region. */\n+\tif (data-\u003ecrypt_offset \u003c (PAGE_SIZE * CHUNK_SIZE)) {\n+\t\tsize_t pg_idx = data-\u003ecrypt_offset \u003e\u003e PAGE_SHIFT;\n+\t\tsize_t pg_off = data-\u003ecrypt_offset \u0026 (PAGE_SIZE - 1);\n+\n+\t\t*buf = data-\u003ecrypt_pages[pg_idx] + pg_off;\n+\t\tsize_avail = PAGE_SIZE - pg_off;\n+\t} else {\n+\t\t/* Use offsets just beyond the size to return the tag. */\n+\t\ttag_off = data-\u003ecrypt_offset - (PAGE_SIZE * CHUNK_SIZE);\n+\t\tif (tag_off \u003e SNAPSHOT_AUTH_TAG_SIZE)\n+\t\t\ttag_off = SNAPSHOT_AUTH_TAG_SIZE;\n+\n+\t\t*buf = data-\u003eauth_tag + tag_off;\n+\t\tsize_avail = SNAPSHOT_AUTH_TAG_SIZE - tag_off;\n+\t}\n+\n+\tif (data-\u003ecrypt_meta_size \u0026\u0026\n+\t data-\u003ecrypt_stream_total \u003c data-\u003ecrypt_meta_size) {\n+\t\tu64 meta_avail = data-\u003ecrypt_meta_size - data-\u003ecrypt_stream_total;\n+\n+\t\tsize_avail = min_t(u64, size_avail, meta_avail);\n+\t}\n+\n+\treturn size_avail;\n+}\n+\n+ssize_t snapshot_read_encrypted(struct snapshot_data *data,\n+\t\t\t\tchar __user *buf, size_t count, loff_t *offp)\n+{\n+\tsize_t copy_size;\n+\tsize_t not_done;\n+\tsize_t pg_off;\n+\tvoid *src;\n+\tssize_t src_size;\n+\n+\tif (!count)\n+\t\treturn 0;\n+\n+\tpg_off = *offp \u0026 (PAGE_SIZE - 1);\n+\tcount = min_t(size_t, count, PAGE_SIZE - pg_off);\n+\n+\tsrc_size = snapshot_read_next_encrypted(data, \u0026src);\n+\tif (src_size \u003c= 0)\n+\t\treturn src_size;\n+\n+\tcopy_size = min_t(size_t, count, src_size);\n+\tnot_done = copy_to_user(buf, src, copy_size);\n+\tcopy_size -= not_done;\n+\tif (!copy_size)\n+\t\treturn -EFAULT;\n+\n+\tdata-\u003ecrypt_offset += copy_size;\n+\t*offp += copy_size;\n+\tsnapshot_record_encrypted_read(data, copy_size);\n+\treturn copy_size;\n+}\n+\n+ssize_t snapshot_write_encrypted(struct snapshot_data *data,\n+\t\t\t\t const char __user *buf, size_t count,\n+\t\t\t\t loff_t *offp)\n+{\n+\tsize_t copy_size;\n+\tsize_t not_done;\n+\tsize_t pg_off;\n+\tvoid *dst;\n+\tssize_t dst_size;\n+\tint rc;\n+\n+\tif (!count)\n+\t\treturn 0;\n+\n+\tpg_off = *offp \u0026 (PAGE_SIZE - 1);\n+\tcount = min_t(size_t, count, PAGE_SIZE - pg_off);\n+\n+\tdst_size = snapshot_write_next_encrypted(data, \u0026dst);\n+\tif (dst_size \u003c= 0)\n+\t\treturn dst_size;\n+\n+\tcopy_size = min_t(size_t, count, dst_size);\n+\tnot_done = copy_from_user(dst, buf, copy_size);\n+\tcopy_size -= not_done;\n+\tif (!copy_size)\n+\t\treturn -EFAULT;\n+\n+\tdata-\u003ecrypt_offset += copy_size;\n+\tdata-\u003ecrypt_stream_total += copy_size;\n+\t/*\n+\t * Drain the encrypted buffer if it's full, or if we hit the end of the\n+\t * encrypted metadata and need a key change before user data pages.\n+\t */\n+\tif (data-\u003ecrypt_offset \u003e=\n+\t (PAGE_SIZE * CHUNK_SIZE) + SNAPSHOT_AUTH_TAG_SIZE ||\n+\t (data-\u003ecrypt_meta_size \u0026\u0026\n+\t data-\u003ecrypt_stream_total == data-\u003ecrypt_meta_size)) {\n+\t\trc = snapshot_decrypt_drain(data);\n+\t\tif (rc \u003c 0)\n+\t\t\treturn rc;\n+\t}\n+\n+\t*offp += copy_size;\n+\treturn copy_size;\n+}\n+\n+static void snapshot_reset_encryption_state(struct snapshot_data *data)\n+{\n+\tdata-\u003ecrypt_offset = 0;\n+\tdata-\u003ecrypt_size = 0;\n+\tdata-\u003ecrypt_total = 0;\n+\tdata-\u003ecrypt_stream_total = 0;\n+\tdata-\u003enonce_low = 0;\n+\tdata-\u003enonce_high = 0;\n+\tdata-\u003emeta_size = 0;\n+\tdata-\u003ecrypt_meta_size = 0;\n+\tdata-\u003euser_key_valid = false;\n+\tspin_lock(\u0026data-\u003ecrypt_lock);\n+\tdata-\u003ecrypt_bytes_read = 0;\n+\tdata-\u003ecrypt_swap_reserved = 0;\n+\tdata-\u003ecrypt_header_reserved = 0;\n+\tspin_unlock(\u0026data-\u003ecrypt_lock);\n+\tmemset(data-\u003eauth_tag, 0, sizeof(data-\u003eauth_tag));\n+}\n+\n+void snapshot_teardown_encryption(struct snapshot_data *data)\n+{\n+\tint i;\n+\n+\tif (data-\u003eaead_req) {\n+\t\taead_request_free(data-\u003eaead_req);\n+\t\tdata-\u003eaead_req = NULL;\n+\t}\n+\n+\tif (data-\u003eaead_tfm) {\n+\t\tcrypto_free_aead(data-\u003eaead_tfm);\n+\t\tdata-\u003eaead_tfm = NULL;\n+\t}\n+\n+\tfor (i = 0; i \u003c CHUNK_SIZE; i++) {\n+\t\tif (data-\u003ecrypt_pages[i]) {\n+\t\t\tfree_page((unsigned long)data-\u003ecrypt_pages[i]);\n+\t\t\tdata-\u003ecrypt_pages[i] = NULL;\n+\t\t}\n+\t}\n+\n+\tmemzero_explicit(data-\u003eencryption_key, sizeof(data-\u003eencryption_key));\n+\tmemzero_explicit(data-\u003euser_key, sizeof(data-\u003euser_key));\n+\tsnapshot_reset_encryption_state(data);\n+}\n+\n+static int snapshot_setup_encryption_common(struct snapshot_data *data)\n+{\n+\tint i, rc;\n+\n+\t/* This only works once per hibernate. */\n+\tif (data-\u003eaead_tfm)\n+\t\treturn -EINVAL;\n+\n+\tsnapshot_reset_encryption_state(data);\n+\tmemset(data-\u003ecrypt_pages, 0, sizeof(data-\u003ecrypt_pages));\n+\n+\t/* Set up the encryption transform */\n+\tdata-\u003eaead_tfm = crypto_alloc_aead(\"gcm(aes)\", 0, 0);\n+\tif (IS_ERR(data-\u003eaead_tfm)) {\n+\t\trc = PTR_ERR(data-\u003eaead_tfm);\n+\t\tdata-\u003eaead_tfm = NULL;\n+\t\treturn rc;\n+\t}\n+\n+\trc = -ENOMEM;\n+\tdata-\u003eaead_req = aead_request_alloc(data-\u003eaead_tfm, GFP_KERNEL);\n+\tif (!data-\u003eaead_req)\n+\t\tgoto setup_fail;\n+\n+\t/* Allocate the staging area */\n+\tfor (i = 0; i \u003c CHUNK_SIZE; i++) {\n+\t\tdata-\u003ecrypt_pages[i] = (void *)__get_free_page(GFP_KERNEL);\n+\t\tif (!data-\u003ecrypt_pages[i])\n+\t\t\tgoto setup_fail;\n+\t}\n+\n+\tsg_init_table(data-\u003esg, CHUNK_SIZE + 2);\n+\n+\t/*\n+\t * The associated data will be the offset so that blocks can't be\n+\t * rearranged.\n+\t */\n+\taead_request_set_ad(data-\u003eaead_req, sizeof(data-\u003ecrypt_total));\n+\trc = crypto_aead_setauthsize(data-\u003eaead_tfm, SNAPSHOT_AUTH_TAG_SIZE);\n+\tif (rc)\n+\t\tgoto setup_fail;\n+\n+\treturn 0;\n+\n+setup_fail:\n+\tsnapshot_teardown_encryption(data);\n+\treturn rc;\n+}\n+\n+int snapshot_get_encryption_key(struct snapshot_data *data,\n+\t\t\t\tstruct uswsusp_key_blob __user *key)\n+{\n+\tstruct snapshot_wrapped_key wrapped = {};\n+\tu8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring = {};\n+\tu8 nonce[USWSUSP_KEY_NONCE_SIZE];\n+\tint rc;\n+\n+\t/* Don't pull a random key from a world that can be reset. */\n+\tif (data-\u003eready)\n+\t\treturn -EPIPE;\n+\n+\trc = snapshot_setup_encryption_common(data);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Build a random starting nonce. */\n+\tget_random_bytes(nonce, sizeof(nonce));\n+\tmemcpy(\u0026data-\u003enonce_low, \u0026nonce[0], sizeof(data-\u003enonce_low));\n+\tmemcpy(\u0026data-\u003enonce_high, \u0026nonce[8], sizeof(data-\u003enonce_high));\n+\n+\t/* Build and install a random image encryption key. */\n+\tget_random_bytes(image_key, sizeof(image_key));\n+\trc = snapshot_install_image_key(data, image_key);\n+\tif (rc)\n+\t\tgoto fail;\n+\n+\trc = snapshot_wrap_image_key(image_key, \u0026wrapped);\n+\tif (rc)\n+\t\tgoto fail;\n+\n+\t/* Hand the wrapped key and clear nonce back to user mode. */\n+\trc = put_user(sizeof(wrapped), \u0026key-\u003eblob_len);\n+\tif (rc)\n+\t\tgoto fail;\n+\n+\tBUILD_BUG_ON(sizeof(wrapped) \u003e\n+\t\t sizeof(((struct uswsusp_key_blob *)0)-\u003eblob));\n+\tif (copy_to_user(\u0026key-\u003eblob, \u0026wrapped, sizeof(wrapped))) {\n+\t\trc = -EFAULT;\n+\t\tgoto fail;\n+\t}\n+\n+\tif (copy_to_user(\u0026key-\u003enonce, \u0026nonce, sizeof(nonce))) {\n+\t\trc = -EFAULT;\n+\t\tgoto fail;\n+\t}\n+\n+\tmemzero_explicit(image_key, sizeof(image_key));\n+\tmemzero_explicit(\u0026wrapped, sizeof(wrapped));\n+\treturn 0;\n+\n+fail:\n+\tmemzero_explicit(image_key, sizeof(image_key));\n+\tmemzero_explicit(\u0026wrapped, sizeof(wrapped));\n+\tsnapshot_teardown_encryption(data);\n+\treturn rc;\n+}\n+\n+int snapshot_set_encryption_key(struct snapshot_data *data,\n+\t\t\t\tstruct uswsusp_key_blob __user *key)\n+{\n+\tstruct uswsusp_key_blob *blob;\n+\tstruct snapshot_wrapped_key wrapped = {};\n+\tu8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring = {};\n+\tint rc;\n+\n+\t/* It's too late if data's been pushed in. */\n+\tif (data-\u003ehandle.cur)\n+\t\treturn -EPIPE;\n+\n+\trc = snapshot_setup_encryption_common(data);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Load the key from user mode. */\n+\tblob = memdup_user(key, sizeof(*blob));\n+\tif (IS_ERR(blob)) {\n+\t\trc = PTR_ERR(blob);\n+\t\tgoto crypto_setup_fail;\n+\t}\n+\n+\tif (blob-\u003eblob_len != sizeof(wrapped)) {\n+\t\trc = -EINVAL;\n+\t\tgoto out_blob;\n+\t}\n+\n+\tmemcpy(\u0026wrapped, blob-\u003eblob, sizeof(wrapped));\n+\trc = snapshot_unwrap_image_key(\u0026wrapped, image_key);\n+\tif (rc)\n+\t\tgoto out_blob;\n+\n+\trc = snapshot_install_image_key(data, image_key);\n+\tif (rc)\n+\t\tgoto out_blob;\n+\n+\t/* Load the starting nonce. */\n+\tmemcpy(\u0026data-\u003enonce_low, \u0026blob-\u003enonce[0], sizeof(data-\u003enonce_low));\n+\tmemcpy(\u0026data-\u003enonce_high, \u0026blob-\u003enonce[8], sizeof(data-\u003enonce_high));\n+\n+out_blob:\n+\tkfree_sensitive(blob);\n+\tmemzero_explicit(\u0026wrapped, sizeof(wrapped));\n+\tmemzero_explicit(image_key, sizeof(image_key));\n+\tif (rc)\n+\t\tgoto crypto_setup_fail;\n+\n+\treturn 0;\n+\n+crypto_setup_fail:\n+\tmemzero_explicit(\u0026wrapped, sizeof(wrapped));\n+\tmemzero_explicit(image_key, sizeof(image_key));\n+\tsnapshot_teardown_encryption(data);\n+\treturn rc;\n+}\n+\n+static loff_t snapshot_encrypted_byte_count(loff_t plain_size)\n+{\n+\tloff_t pages = plain_size \u003e\u003e PAGE_SHIFT;\n+\tloff_t chunks = (pages + (CHUNK_SIZE - 1)) / CHUNK_SIZE;\n+\t/*\n+\t * The encrypted size is the normal size, plus a stitched in\n+\t * authentication tag for every chunk of pages.\n+\t */\n+\treturn plain_size + (chunks * SNAPSHOT_AUTH_TAG_SIZE);\n+}\n+\n+static loff_t snapshot_encrypted_split_byte_count(loff_t raw_size,\n+\t\t\t\t\t\t loff_t meta_plain_size)\n+{\n+\tif (raw_size \u003c= meta_plain_size)\n+\t\treturn snapshot_encrypted_byte_count(raw_size);\n+\n+\treturn snapshot_encrypted_byte_count(meta_plain_size) +\n+\t\tsnapshot_encrypted_byte_count(raw_size - meta_plain_size);\n+}\n+\n+int snapshot_set_user_key(struct snapshot_data *data,\n+\t\t\t struct uswsusp_user_key __user *key)\n+{\n+\tstruct uswsusp_user_key user_key = {};\n+\tunsigned int key_len;\n+\tu64 size;\n+\tint rc;\n+\n+\tif (!snapshot_encryption_enabled(data))\n+\t\treturn -EINVAL;\n+\n+\tif (copy_from_user(\u0026user_key, key, sizeof(struct uswsusp_user_key)))\n+\t\treturn -EFAULT;\n+\n+\tBUILD_BUG_ON(sizeof(data-\u003euser_key) \u003c sizeof(user_key.key));\n+\trc = -EINVAL;\n+\tif (user_key.reserved)\n+\t\tgoto out;\n+\tif (user_key.key_len \u003e sizeof(data-\u003euser_key))\n+\t\tgoto out;\n+\tif (user_key.key_len \u003c 8)\n+\t\tgoto out;\n+\n+\tif (data-\u003emode == O_RDONLY \u0026\u0026 !data-\u003eready) {\n+\t\trc = -ENODATA;\n+\t\tgoto out;\n+\t}\n+\n+\t/*\n+\t * Return the metadata size, the number of bytes that can be fed in before\n+\t * the user data key is needed at resume time.\n+\t */\n+\tif (data-\u003emode == O_WRONLY \u0026\u0026 !data-\u003emeta_size) {\n+\t\tif (user_key.meta_size \u003c PAGE_SIZE + SNAPSHOT_AUTH_TAG_SIZE)\n+\t\t\tgoto out;\n+\n+\t\tdata-\u003ecrypt_meta_size = user_key.meta_size;\n+\t\tsize = data-\u003ecrypt_meta_size;\n+\t} else {\n+\t\tsnapshot_set_meta_size(data,\n+\t\t\t\t snapshot_get_meta_page_count() \u003c\u003c PAGE_SHIFT);\n+\t\tsize = data-\u003ecrypt_meta_size;\n+\t}\n+\n+\trc = put_user(size, \u0026key-\u003emeta_size);\n+\tif (rc)\n+\t\tgoto out;\n+\n+\tkey_len = user_key.key_len;\n+\n+\t/* Don't allow it if it's too late. */\n+\tif ((data-\u003emeta_size \u0026\u0026 data-\u003ecrypt_total \u003e data-\u003emeta_size) ||\n+\t (data-\u003ecrypt_meta_size \u0026\u0026\n+\t data-\u003ecrypt_stream_total \u003e data-\u003ecrypt_meta_size)) {\n+\t\trc = -EBUSY;\n+\t\tgoto out;\n+\t}\n+\n+\tmemset(data-\u003euser_key, 0, sizeof(data-\u003euser_key));\n+\tmemcpy(data-\u003euser_key, user_key.key, key_len);\n+\tdata-\u003euser_key_valid = true;\n+\t/* Install the key if the user is just under the wire. */\n+\trc = snapshot_check_user_key_switch(data);\n+\tif (rc)\n+\t\tgoto out;\n+\n+\trc = 0;\n+\n+out:\n+\tmemzero_explicit(\u0026user_key, sizeof(user_key));\n+\treturn rc;\n+}\n+\n+loff_t snapshot_get_encrypted_image_size(struct snapshot_data *data,\n+\t\t\t\t\t loff_t raw_size)\n+{\n+\tloff_t meta_plain_size = data-\u003emeta_size;\n+\tloff_t split_size;\n+\n+\tif (!meta_plain_size)\n+\t\tmeta_plain_size = snapshot_get_meta_page_count() \u003c\u003c PAGE_SHIFT;\n+\n+\tsplit_size = snapshot_encrypted_split_byte_count(raw_size,\n+\t\t\t\t\t\t\t meta_plain_size);\n+\tif (!data-\u003euser_key_valid)\n+\t\treturn max(snapshot_encrypted_byte_count(raw_size), split_size);\n+\n+\treturn split_size;\n+}\n+\n+int snapshot_finalize_decrypted_image(struct snapshot_data *data)\n+{\n+\tint rc;\n+\n+\tif (data-\u003ecrypt_offset != 0) {\n+\t\trc = snapshot_decrypt_drain(data);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c\nindex d933b5b2c05d4..02400ac125c58 100644\n--- a/kernel/power/snapshot.c\n+++ b/kernel/power/snapshot.c\n@@ -2177,6 +2177,11 @@ unsigned long snapshot_get_image_size(void)\n \treturn nr_copy_pages + nr_meta_pages + 1;\n }\n \n+unsigned long snapshot_get_meta_page_count(void)\n+{\n+\treturn nr_meta_pages + 1;\n+}\n+\n static int init_header(struct swsusp_info *info)\n {\n \tmemset(info, 0, sizeof(struct swsusp_info));\ndiff --git a/kernel/power/swap.c b/kernel/power/swap.c\nindex c626e9dc3c1c1..3bdd58d5b62cb 100644\n--- a/kernel/power/swap.c\n+++ b/kernel/power/swap.c\n@@ -166,6 +166,104 @@ static int swsusp_extents_insert(unsigned long swap_offset)\n \treturn 0;\n }\n \n+static bool swsusp_extents_contain_range(unsigned long swap_offset,\n+\t\t\t\t\t unsigned long nr_pages)\n+{\n+\tstruct rb_node *node = swsusp_extents.rb_node;\n+\tstruct swsusp_extent *ext;\n+\tunsigned long end;\n+\n+\tif (!nr_pages)\n+\t\treturn false;\n+\n+\tend = swap_offset + nr_pages - 1;\n+\tif (end \u003c swap_offset)\n+\t\treturn false;\n+\n+\twhile (node) {\n+\t\text = rb_entry(node, struct swsusp_extent, node);\n+\t\tif (swap_offset \u003c ext-\u003estart)\n+\t\t\tnode = node-\u003erb_left;\n+\t\telse if (swap_offset \u003e ext-\u003eend)\n+\t\t\tnode = node-\u003erb_right;\n+\t\telse\n+\t\t\tgoto found;\n+\t}\n+\n+\treturn false;\n+\n+found:\n+\tfor (;;) {\n+\t\tif (swap_offset \u003c ext-\u003estart)\n+\t\t\treturn false;\n+\t\tif (end \u003c= ext-\u003eend)\n+\t\t\treturn true;\n+\t\tif (ext-\u003eend == (unsigned long)-1)\n+\t\t\treturn false;\n+\n+\t\tswap_offset = ext-\u003eend + 1;\n+\t\tnode = rb_next(\u0026ext-\u003enode);\n+\t\tif (!node)\n+\t\t\treturn false;\n+\n+\t\text = rb_entry(node, struct swsusp_extent, node);\n+\t}\n+}\n+\n+bool swsusp_swap_range_allocated(int swap, loff_t pos, size_t count)\n+{\n+\tu64 block;\n+\tu64 end;\n+\tu64 end_block;\n+\n+\tif (swap \u003c 0 || pos \u003c 0 || !count)\n+\t\treturn false;\n+\n+\tend = (u64)pos + count - 1;\n+\tif (end \u003c (u64)pos)\n+\t\treturn false;\n+\n+\tblock = (u64)pos \u003e\u003e PAGE_SHIFT;\n+\tend_block = end \u003e\u003e PAGE_SHIFT;\n+\n+\tfor (;;) {\n+\t\tu64 remaining_pages = end_block - block + 1;\n+\t\tpgoff_t swap_offset;\n+\t\tpgoff_t mapped_pages;\n+\t\tsector_t page_block = block;\n+\t\tu64 pages;\n+\n+\t\tif (!remaining_pages)\n+\t\t\treturn false;\n+\t\tif ((u64)page_block != block)\n+\t\t\treturn false;\n+\t\tif (swapdev_block_to_extent(swap, page_block, \u0026swap_offset,\n+\t\t\t\t\t \u0026mapped_pages))\n+\t\t\treturn false;\n+\t\tif (!mapped_pages)\n+\t\t\treturn false;\n+\t\tpages = min_t(u64, mapped_pages, remaining_pages);\n+\t\tif ((u64)(unsigned long)pages != pages)\n+\t\t\treturn false;\n+\t\tif (!swsusp_extents_contain_range(swap_offset, pages))\n+\t\t\treturn false;\n+\t\tif (pages == remaining_pages)\n+\t\t\treturn true;\n+\t\tblock += pages;\n+\t}\n+}\n+\n+u64 swsusp_swap_map_bytes(u64 image_bytes)\n+{\n+\tu64 image_pages = DIV_ROUND_UP_ULL(image_bytes, PAGE_SIZE);\n+\tu64 map_pages = image_pages / MAP_PAGE_ENTRIES + 1;\n+\n+\tif (map_pages \u003e U64_MAX \u003e\u003e PAGE_SHIFT)\n+\t\treturn U64_MAX;\n+\n+\treturn map_pages \u003c\u003c PAGE_SHIFT;\n+}\n+\n sector_t alloc_swapdev_block(int swap)\n {\n \tunsigned long offset;\ndiff --git a/kernel/power/user.c b/kernel/power/user.c\nindex d0fcfba7ac235..57b8ce83a626f 100644\n--- a/kernel/power/user.c\n+++ b/kernel/power/user.c\n@@ -21,54 +21,185 @@\n #include \u003clinux/console.h\u003e\n #include \u003clinux/cpu.h\u003e\n #include \u003clinux/freezer.h\u003e\n+#include \u003clinux/pid.h\u003e\n+#include \u003clinux/sched/signal.h\u003e\n+#include \u003clinux/security.h\u003e\n \n #include \u003clinux/uaccess.h\u003e\n \n #include \"power.h\"\n+#include \"user.h\"\n \n static bool need_wait;\n-\n-static struct snapshot_data {\n-\tstruct snapshot_handle handle;\n-\tint swap;\n-\tint mode;\n-\tbool frozen;\n-\tbool ready;\n-\tbool platform_support;\n-\tbool free_bitmaps;\n-\tdev_t dev;\n-} snapshot_state;\n+struct snapshot_data snapshot_state;\n \n int is_hibernate_resume_dev(dev_t dev)\n {\n \treturn hibernation_available() \u0026\u0026 snapshot_state.dev == dev;\n }\n \n+#if defined(CONFIG_ENCRYPTED_HIBERNATION)\n+static bool snapshot_encrypted_output_active(struct snapshot_data *data, dev_t dev)\n+{\n+\treturn data-\u003eencryption_required \u0026\u0026 data-\u003emode == O_RDONLY \u0026\u0026\n+\t\tdata-\u003eready \u0026\u0026 data-\u003edev == dev \u0026\u0026\n+\t\tdata-\u003eowner_tgid == task_tgid(current) \u0026\u0026\n+\t\tsnapshot_encryption_enabled(data);\n+}\n+\n+static bool snapshot_header_write_range(struct snapshot_data *data,\n+\t\t\t\t\tloff_t pos, size_t count)\n+{\n+\tloff_t header_offset = data-\u003eswap_header_offset;\n+\tloff_t offset;\n+\n+\tif (pos \u003c header_offset)\n+\t\treturn false;\n+\n+\toffset = pos - header_offset;\n+\treturn offset \u003c PAGE_SIZE \u0026\u0026 count \u003c= PAGE_SIZE - offset;\n+}\n+\n+static u64 snapshot_swap_write_budget(struct snapshot_data *data)\n+{\n+\tu64 image_bytes = round_up(data-\u003ecrypt_bytes_read, PAGE_SIZE);\n+\tu64 map_bytes = swsusp_swap_map_bytes(data-\u003ecrypt_bytes_read);\n+\tu64 budget = image_bytes + map_bytes;\n+\n+\tif (image_bytes \u003c data-\u003ecrypt_bytes_read || budget \u003c image_bytes)\n+\t\treturn U64_MAX;\n+\n+\treturn budget;\n+}\n+\n+static void snapshot_reset_swap_write_reservation(struct snapshot_data *data)\n+{\n+\tspin_lock(\u0026data-\u003ecrypt_lock);\n+\tdata-\u003ecrypt_swap_reserved = 0;\n+\tspin_unlock(\u0026data-\u003ecrypt_lock);\n+}\n+#endif\n+\n+enum hibernate_snapshot_write\n+hibernate_snapshot_write_begin(dev_t dev, loff_t pos, size_t count)\n+{\n+#if defined(CONFIG_ENCRYPTED_HIBERNATION)\n+\tstruct snapshot_data *data = \u0026snapshot_state;\n+\tenum hibernate_snapshot_write type = HIBERNATE_SNAPSHOT_WRITE_NONE;\n+\tbool image_range_allocated;\n+\tu64 swap_budget;\n+\n+\tif (!count || !snapshot_encrypted_output_active(data, dev))\n+\t\treturn HIBERNATE_SNAPSHOT_WRITE_NONE;\n+\n+\tmutex_lock(\u0026system_transition_mutex);\n+\n+\tif (!snapshot_encrypted_output_active(data, dev))\n+\t\tgoto unlock;\n+\n+\timage_range_allocated = swsusp_swap_range_allocated(data-\u003eswap, pos, count);\n+\n+\tspin_lock(\u0026data-\u003ecrypt_lock);\n+\tswap_budget = snapshot_swap_write_budget(data);\n+\tif (snapshot_header_write_range(data, pos, count) \u0026\u0026\n+\t data-\u003ecrypt_header_reserved \u003c= PAGE_SIZE \u0026\u0026\n+\t PAGE_SIZE - data-\u003ecrypt_header_reserved \u003e= count) {\n+\t\tdata-\u003ecrypt_header_reserved += count;\n+\t\ttype = HIBERNATE_SNAPSHOT_WRITE_HEADER;\n+\t} else if (image_range_allocated \u0026\u0026\n+\t\t swap_budget \u003e= data-\u003ecrypt_swap_reserved \u0026\u0026\n+\t\t swap_budget - data-\u003ecrypt_swap_reserved \u003e= count) {\n+\t\tdata-\u003ecrypt_swap_reserved += count;\n+\t\ttype = HIBERNATE_SNAPSHOT_WRITE_IMAGE;\n+\t}\n+\tspin_unlock(\u0026data-\u003ecrypt_lock);\n+\n+\tif (type == HIBERNATE_SNAPSHOT_WRITE_NONE)\n+\t\tgoto unlock;\n+\n+\treturn type;\n+\n+unlock:\n+\tmutex_unlock(\u0026system_transition_mutex);\n+\treturn HIBERNATE_SNAPSHOT_WRITE_NONE;\n+#else\n+\treturn HIBERNATE_SNAPSHOT_WRITE_NONE;\n+#endif\n+}\n+\n+void hibernate_snapshot_write_end(enum hibernate_snapshot_write type,\n+\t\t\t\t size_t reserved, ssize_t written)\n+{\n+#if defined(CONFIG_ENCRYPTED_HIBERNATION)\n+\tstruct snapshot_data *data = \u0026snapshot_state;\n+\tu64 *reserved_total;\n+\tsize_t unused;\n+\n+\tif (type == HIBERNATE_SNAPSHOT_WRITE_NONE)\n+\t\treturn;\n+\tif (!reserved)\n+\t\tgoto unlock;\n+\n+\tif (type == HIBERNATE_SNAPSHOT_WRITE_HEADER)\n+\t\tunused = reserved;\n+\telse if (written \u003c= 0)\n+\t\tunused = reserved;\n+\telse if (written \u003c reserved)\n+\t\tunused = reserved - written;\n+\telse\n+\t\tgoto unlock;\n+\n+\treserved_total = type == HIBERNATE_SNAPSHOT_WRITE_HEADER ?\n+\t\t\u0026data-\u003ecrypt_header_reserved : \u0026data-\u003ecrypt_swap_reserved;\n+\n+\tspin_lock(\u0026data-\u003ecrypt_lock);\n+\t*reserved_total -= min_t(u64, *reserved_total, unused);\n+\tspin_unlock(\u0026data-\u003ecrypt_lock);\n+\n+unlock:\n+\tmutex_unlock(\u0026system_transition_mutex);\n+#endif\n+}\n+\n+static bool snapshot_encryption_required(struct snapshot_data *data)\n+{\n+\tdata-\u003eencryption_required = security_locked_down(LOCKDOWN_HIBERNATION);\n+\treturn data-\u003eencryption_required;\n+}\n+\n static int snapshot_open(struct inode *inode, struct file *filp)\n {\n \tstruct snapshot_data *data;\n \tunsigned int sleep_flags;\n \tint error;\n \n-\tif (!hibernation_available())\n+\tif (!hibernation_snapshot_dev_available())\n \t\treturn -EPERM;\n \n \tsleep_flags = lock_system_sleep();\n \n \tif (!hibernate_acquire()) {\n \t\terror = -EBUSY;\n-\t\tgoto Unlock;\n+\t\tgoto unlock;\n \t}\n \n \tif ((filp-\u003ef_flags \u0026 O_ACCMODE) == O_RDWR) {\n \t\thibernate_release();\n \t\terror = -ENOSYS;\n-\t\tgoto Unlock;\n+\t\tgoto unlock;\n \t}\n \tnonseekable_open(inode, filp);\n \tdata = \u0026snapshot_state;\n \tfilp-\u003eprivate_data = data;\n \tmemset(\u0026data-\u003ehandle, 0, sizeof(struct snapshot_handle));\n+#if defined(CONFIG_ENCRYPTED_HIBERNATION)\n+\tspin_lock_init(\u0026data-\u003ecrypt_lock);\n+\tdata-\u003ecrypt_bytes_read = 0;\n+\tdata-\u003ecrypt_swap_reserved = 0;\n+\tdata-\u003ecrypt_header_reserved = 0;\n+\tdata-\u003eswap_header_offset = 0;\n+\tdata-\u003eowner_tgid = NULL;\n+#endif\n \tif ((filp-\u003ef_flags \u0026 O_ACCMODE) == O_RDONLY) {\n \t\t/* Hibernating. The image device should be accessible. */\n \t\tdata-\u003eswap = pin_hibernation_swap_type(swsusp_resume_device, 0);\n@@ -99,8 +230,13 @@ static int snapshot_open(struct inode *inode, struct file *filp)\n \tdata-\u003eready = false;\n \tdata-\u003eplatform_support = false;\n \tdata-\u003edev = 0;\n+\tdata-\u003eencryption_required = snapshot_encryption_required(data);\n+#if defined(CONFIG_ENCRYPTED_HIBERNATION)\n+\tif (!error)\n+\t\tdata-\u003eowner_tgid = get_task_pid(current, PIDTYPE_TGID);\n+#endif\n \n- Unlock:\n+ unlock:\n \tunlock_system_sleep(sleep_flags);\n \n \treturn error;\n@@ -116,6 +252,10 @@ static int snapshot_release(struct inode *inode, struct file *filp)\n \tswsusp_free();\n \tdata = filp-\u003eprivate_data;\n \tdata-\u003edev = 0;\n+#if defined(CONFIG_ENCRYPTED_HIBERNATION)\n+\tput_pid(data-\u003eowner_tgid);\n+\tdata-\u003eowner_tgid = NULL;\n+#endif\n \tfree_all_swap_pages(data-\u003eswap);\n \tunpin_hibernation_swap_type(data-\u003eswap);\n \tif (data-\u003efrozen) {\n@@ -125,6 +265,7 @@ static int snapshot_release(struct inode *inode, struct file *filp)\n \t} else if (data-\u003efree_bitmaps) {\n \t\tfree_basic_memory_bitmaps();\n \t}\n+\tsnapshot_teardown_encryption(data);\n \tpm_notifier_call_chain(data-\u003emode == O_RDONLY ?\n \t\t\tPM_POST_HIBERNATION : PM_POST_RESTORE);\n \thibernate_release();\n@@ -147,12 +288,23 @@ static ssize_t snapshot_read(struct file *filp, char __user *buf,\n \tdata = filp-\u003eprivate_data;\n \tif (!data-\u003eready) {\n \t\tres = -ENODATA;\n-\t\tgoto Unlock;\n+\t\tgoto unlock;\n+\t}\n+\tif (snapshot_encryption_required(data) \u0026\u0026\n+\t !snapshot_encryption_enabled(data)) {\n+\t\tres = -EPERM;\n+\t\tgoto unlock;\n+\t}\n+\n+\tif (snapshot_encryption_enabled(data)) {\n+\t\tres = snapshot_read_encrypted(data, buf, count, offp);\n+\t\tgoto unlock;\n \t}\n+\n \tif (!pg_offp) { /* on page boundary? */\n \t\tres = snapshot_read_next(\u0026data-\u003ehandle);\n \t\tif (res \u003c= 0)\n-\t\t\tgoto Unlock;\n+\t\t\tgoto unlock;\n \t} else {\n \t\tres = PAGE_SIZE - pg_offp;\n \t}\n@@ -162,7 +314,7 @@ static ssize_t snapshot_read(struct file *filp, char __user *buf,\n \tif (res \u003e 0)\n \t\t*offp += res;\n \n- Unlock:\n+ unlock:\n \tunlock_system_sleep(sleep_flags);\n \n \treturn res;\n@@ -185,6 +337,17 @@ static ssize_t snapshot_write(struct file *filp, const char __user *buf,\n \n \tdata = filp-\u003eprivate_data;\n \n+\tif (snapshot_encryption_required(data) \u0026\u0026\n+\t !snapshot_encryption_enabled(data)) {\n+\t\tres = -EPERM;\n+\t\tgoto unlock;\n+\t}\n+\n+\tif (snapshot_encryption_enabled(data)) {\n+\t\tres = snapshot_write_encrypted(data, buf, count, offp);\n+\t\tgoto unlock;\n+\t}\n+\n \tif (!pg_offp) {\n \t\tres = snapshot_write_next(\u0026data-\u003ehandle);\n \t\tif (res \u003c= 0)\n@@ -252,6 +415,9 @@ static int snapshot_set_swap_area(struct snapshot_data *data,\n \tif (data-\u003eswap \u003c 0)\n \t\treturn swdev ? -ENODEV : -EINVAL;\n \tdata-\u003edev = swdev;\n+#if defined(CONFIG_ENCRYPTED_HIBERNATION)\n+\tdata-\u003eswap_header_offset = (loff_t)offset \u003c\u003c PAGE_SHIFT;\n+#endif\n \treturn 0;\n }\n \n@@ -318,6 +484,11 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,\n \t\t\terror = -EPERM;\n \t\t\tbreak;\n \t\t}\n+\t\tif (snapshot_encryption_required(data) \u0026\u0026\n+\t\t !snapshot_encryption_enabled(data)) {\n+\t\t\terror = -EPERM;\n+\t\t\tbreak;\n+\t\t}\n \t\tpm_restore_gfp_mask();\n \t\terror = hibernation_snapshot(data-\u003eplatform_support);\n \t\tif (!error) {\n@@ -328,6 +499,17 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,\n \t\tbreak;\n \n \tcase SNAPSHOT_ATOMIC_RESTORE:\n+\t\tif (snapshot_encryption_required(data) \u0026\u0026\n+\t\t !snapshot_encryption_enabled(data)) {\n+\t\t\terror = -EPERM;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (snapshot_encryption_enabled(data)) {\n+\t\t\terror = snapshot_finalize_decrypted_image(data);\n+\t\t\tif (error)\n+\t\t\t\tbreak;\n+\t\t}\n+\n \t\terror = snapshot_write_finalize(\u0026data-\u003ehandle);\n \t\tif (error)\n \t\t\tbreak;\n@@ -346,6 +528,7 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,\n \t\tswsusp_free();\n \t\tmemset(\u0026data-\u003ehandle, 0, sizeof(struct snapshot_handle));\n \t\tdata-\u003eready = false;\n+\t\tsnapshot_teardown_encryption(data);\n \t\t/*\n \t\t * It is necessary to thaw kernel threads here, because\n \t\t * SNAPSHOT_CREATE_IMAGE may be invoked directly after\n@@ -368,6 +551,8 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,\n \t\t}\n \t\tsize = snapshot_get_image_size();\n \t\tsize \u003c\u003c= PAGE_SHIFT;\n+\t\tif (snapshot_encryption_enabled(data))\n+\t\t\tsize = snapshot_get_encrypted_image_size(data, size);\n \t\terror = put_user(size, (loff_t __user *)arg);\n \t\tbreak;\n \n@@ -397,6 +582,9 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,\n \t\t\tbreak;\n \t\t}\n \t\tfree_all_swap_pages(data-\u003eswap);\n+#if defined(CONFIG_ENCRYPTED_HIBERNATION)\n+\t\tsnapshot_reset_swap_write_reservation(data);\n+#endif\n \t\tbreak;\n \n \tcase SNAPSHOT_S2RAM:\n@@ -404,6 +592,11 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,\n \t\t\terror = -EPERM;\n \t\t\tbreak;\n \t\t}\n+\t\tif (snapshot_encryption_required(data) \u0026\u0026\n+\t\t !snapshot_encryption_enabled(data)) {\n+\t\t\terror = -EPERM;\n+\t\t\tbreak;\n+\t\t}\n \t\t/*\n \t\t * Tasks are frozen and the notifiers have been called with\n \t\t * PM_HIBERNATION_PREPARE\n@@ -417,6 +610,11 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,\n \t\tbreak;\n \n \tcase SNAPSHOT_POWER_OFF:\n+\t\tif (snapshot_encryption_required(data) \u0026\u0026\n+\t\t !snapshot_encryption_enabled(data)) {\n+\t\t\terror = -EPERM;\n+\t\t\tbreak;\n+\t\t}\n \t\tif (data-\u003eplatform_support)\n \t\t\terror = hibernation_platform_enter();\n \t\tbreak;\n@@ -425,6 +623,17 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,\n \t\terror = snapshot_set_swap_area(data, (void __user *)arg);\n \t\tbreak;\n \n+\tcase SNAPSHOT_ENABLE_ENCRYPTION:\n+\t\tif (data-\u003emode == O_RDONLY)\n+\t\t\terror = snapshot_get_encryption_key(data, (void __user *)arg);\n+\t\telse\n+\t\t\terror = snapshot_set_encryption_key(data, (void __user *)arg);\n+\t\tbreak;\n+\n+\tcase SNAPSHOT_SET_USER_KEY:\n+\t\terror = snapshot_set_user_key(data, (void __user *)arg);\n+\t\tbreak;\n+\n \tdefault:\n \t\terror = -ENOTTY;\n \n@@ -448,6 +657,8 @@ snapshot_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)\n \tcase SNAPSHOT_ALLOC_SWAP_PAGE:\n \tcase SNAPSHOT_CREATE_IMAGE:\n \tcase SNAPSHOT_SET_SWAP_AREA:\n+\tcase SNAPSHOT_ENABLE_ENCRYPTION:\n+\tcase SNAPSHOT_SET_USER_KEY:\n \t\treturn snapshot_ioctl(file, cmd,\n \t\t\t\t (unsigned long) compat_ptr(arg));\n \tdefault:\ndiff --git a/kernel/power/user.h b/kernel/power/user.h\nnew file mode 100644\nindex 0000000000000..4df2f9ae9ada4\n--- /dev/null\n+++ b/kernel/power/user.h\n@@ -0,0 +1,151 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+\n+#include \u003clinux/crypto.h\u003e\n+#include \u003clinux/scatterlist.h\u003e\n+#include \u003clinux/spinlock.h\u003e\n+#include \u003clinux/suspend_ioctls.h\u003e\n+#include \u003ccrypto/aead.h\u003e\n+#include \u003ccrypto/aes.h\u003e\n+\n+struct pid;\n+\n+#define SNAPSHOT_ENCRYPTION_KEY_SIZE AES_KEYSIZE_128\n+#define SNAPSHOT_AUTH_TAG_SIZE 16\n+\n+/* Define the number of pages in a single AEAD encryption chunk. */\n+#define CHUNK_SIZE 16\n+\n+struct snapshot_data {\n+\tstruct snapshot_handle handle;\n+\tint swap;\n+\tint mode;\n+\tbool frozen;\n+\tbool ready;\n+\tbool platform_support;\n+\tbool free_bitmaps;\n+\tbool encryption_required;\n+\tdev_t dev;\n+\n+#if defined(CONFIG_ENCRYPTED_HIBERNATION)\n+\tstruct crypto_aead *aead_tfm;\n+\tstruct aead_request *aead_req;\n+\tvoid *crypt_pages[CHUNK_SIZE];\n+\tu8 auth_tag[SNAPSHOT_AUTH_TAG_SIZE];\n+\tstruct scatterlist sg[CHUNK_SIZE + 2]; /* Add room for AD and auth tag. */\n+\tsize_t crypt_offset;\n+\tsize_t crypt_size;\n+\tu64 crypt_total;\n+\tu64 crypt_stream_total;\n+\tu64 nonce_low;\n+\tu64 nonce_high;\n+\tu8 encryption_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring;\n+\tu8 user_key[USWSUSP_USER_KEY_SIZE] __nonstring;\n+\tbool user_key_valid;\n+\tu64 meta_size;\n+\tu64 crypt_meta_size;\n+\t/*\n+\t * Protects crypt_bytes_read and the reservation counters below.\n+\t * crypt_swap_reserved covers encrypted snapshot bytes and userspace\n+\t * generated swap-map pages.\n+\t */\n+\tspinlock_t crypt_lock;\n+\tu64 crypt_bytes_read;\n+\tu64 crypt_swap_reserved;\n+\tu64 crypt_header_reserved;\n+\tloff_t swap_header_offset;\n+\tstruct pid *owner_tgid;\n+#endif\n+\n+};\n+\n+extern struct snapshot_data snapshot_state;\n+\n+/* kernel/power/snapenc.c routines */\n+#if defined(CONFIG_ENCRYPTED_HIBERNATION)\n+\n+ssize_t snapshot_read_encrypted(struct snapshot_data *data,\n+\t\t\t\tchar __user *buf, size_t count, loff_t *offp);\n+\n+ssize_t snapshot_write_encrypted(struct snapshot_data *data,\n+\t\t\t\t const char __user *buf, size_t count,\n+\t\t\t\t loff_t *offp);\n+\n+void snapshot_teardown_encryption(struct snapshot_data *data);\n+int snapshot_get_encryption_key(struct snapshot_data *data,\n+\t\t\t\tstruct uswsusp_key_blob __user *key);\n+\n+int snapshot_set_encryption_key(struct snapshot_data *data,\n+\t\t\t\tstruct uswsusp_key_blob __user *key);\n+\n+int snapshot_set_user_key(struct snapshot_data *data,\n+\t\t\t struct uswsusp_user_key __user *key);\n+\n+int snapshot_store_encryption_seed(const char *buf, size_t count);\n+\n+loff_t snapshot_get_encrypted_image_size(struct snapshot_data *data,\n+\t\t\t\t\t loff_t raw_size);\n+\n+int snapshot_finalize_decrypted_image(struct snapshot_data *data);\n+\n+static inline bool snapshot_encryption_enabled(struct snapshot_data *data)\n+{\n+\treturn data-\u003eaead_tfm;\n+}\n+\n+#else\n+\n+static inline ssize_t snapshot_read_encrypted(struct snapshot_data *data,\n+\t\t\t\t\t char __user *buf, size_t count,\n+\t\t\t\t\t loff_t *offp)\n+{\n+\treturn -ENOTTY;\n+}\n+\n+static inline ssize_t snapshot_write_encrypted(struct snapshot_data *data,\n+\t\t\t\t\t const char __user *buf,\n+\t\t\t\t\t size_t count, loff_t *offp)\n+{\n+\treturn -ENOTTY;\n+}\n+\n+static inline void snapshot_teardown_encryption(struct snapshot_data *data) {}\n+static inline int snapshot_get_encryption_key(struct snapshot_data *data,\n+\t\t\t\t\t struct uswsusp_key_blob __user *key)\n+{\n+\treturn -ENOTTY;\n+}\n+\n+static inline int snapshot_set_encryption_key(struct snapshot_data *data,\n+\t\t\t\t\t struct uswsusp_key_blob __user *key)\n+{\n+\treturn -ENOTTY;\n+}\n+\n+static inline int snapshot_set_user_key(struct snapshot_data *data,\n+\t\t\t\t\tstruct uswsusp_user_key __user *key)\n+{\n+\treturn -ENOTTY;\n+}\n+\n+static inline int snapshot_store_encryption_seed(const char *buf, size_t count)\n+{\n+\treturn -ENOTTY;\n+}\n+\n+static inline loff_t snapshot_get_encrypted_image_size(struct snapshot_data *data,\n+\t\t\t\t\t\t loff_t raw_size)\n+{\n+\treturn raw_size;\n+}\n+\n+static inline int snapshot_finalize_decrypted_image(struct snapshot_data *data)\n+{\n+\treturn -ENOTTY;\n+}\n+\n+static inline bool snapshot_encryption_enabled(struct snapshot_data *data)\n+{\n+\treturn false;\n+}\n+\n+#endif\ndiff --git a/mm/swapfile.c b/mm/swapfile.c\nindex 78b49b0658add..11df0c556f4e9 100644\n--- a/mm/swapfile.c\n+++ b/mm/swapfile.c\n@@ -2366,6 +2366,38 @@ sector_t swapdev_block(int type, pgoff_t offset)\n \treturn se-\u003estart_block + (offset - se-\u003estart_page);\n }\n \n+int swapdev_block_to_extent(int type, sector_t block, pgoff_t *offset,\n+\t\t\t pgoff_t *nr_pages)\n+{\n+\tstruct swap_info_struct *si = swap_type_to_info(type);\n+\tstruct swap_extent *se;\n+\tstruct rb_node *rb;\n+\n+\tif (!si || !(si-\u003eflags \u0026 SWP_WRITEOK))\n+\t\treturn -ENODEV;\n+\n+\tfor (rb = rb_first(\u0026si-\u003eswap_extent_root); rb; rb = rb_next(rb)) {\n+\t\tse = rb_entry(rb, struct swap_extent, rb_node);\n+\t\tif (block \u003e= se-\u003estart_block \u0026\u0026\n+\t\t block - se-\u003estart_block \u003c se-\u003enr_pages) {\n+\t\t\tpgoff_t page = block - se-\u003estart_block;\n+\n+\t\t\t*offset = se-\u003estart_page + page;\n+\t\t\t*nr_pages = se-\u003enr_pages - page;\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn -ENOENT;\n+}\n+\n+int swapdev_block_to_offset(int type, sector_t block, pgoff_t *offset)\n+{\n+\tpgoff_t nr_pages;\n+\n+\treturn swapdev_block_to_extent(type, block, offset, \u0026nr_pages);\n+}\n+\n /*\n * Return either the total number of swap pages of given type, or the number\n * of free pages of that type (depending on @free)\n"
} |
| 2/1 | 2026/08/04 12:12 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"ENCRYPTED_HIBERNATION",
"HIBERNATION_SNAPSHOT_DEV",
"HIBERNATION"
],
"FocusSymbols": [
"snapshot_store_encryption_seed",
"snapshot_get_encryption_key",
"snapshot_set_encryption_key",
"snapshot_set_user_key",
"snapshot_read_encrypted",
"snapshot_write_encrypted",
"snapshot_finalize_decrypted_image",
"hibernate_snapshot_write_begin",
"hibernate_snapshot_write_end",
"swsusp_swap_range_allocated",
"swapdev_block_to_extent"
],
"Reasoning": "The patch introduces kernel-based encryption for userspace hibernation snapshots. It adds new ioctls to `/dev/snapshot` (`SNAPSHOT_ENABLE_ENCRYPTION`, `SNAPSHOT_SET_USER_KEY`), a sysfs interface `/sys/power/snapshot_seed`, and modifies the read/write paths for the snapshot device to support encryption/decryption. It also hooks into block device writes to track swap allocations. These are reachable core kernel changes that should be fuzzed.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit a03c020e7a2c2fff2ea2b4d38087c084d66457aa
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 4 12:12:54 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power
index d38da077905a9..57f6063ef88c9 100644
--- a/Documentation/ABI/testing/sysfs-power
+++ b/Documentation/ABI/testing/sysfs-power
@@ -470,3 +470,17 @@ Description:
Minimum value: 1
Default value: 3
+
+What: /sys/power/snapshot_seed
+Date: July 2026
+Contact: linux-pm@vger.kernel.org
+Description:
+ Write-only file present when CONFIG_ENCRYPTED_HIBERNATION=y.
+
+ Trusted early userspace writes a 32-byte seed, encoded as
+ 64 hexadecimal characters plus an optional newline, before
+ enabling encrypted userspace hibernation snapshots.
+
+ The first successful write locks the seed until the next boot.
+ Writing the same seed again succeeds. Writing a different seed
+ fails with EPERM.
diff --git a/Documentation/power/userland-swsusp.rst b/Documentation/power/userland-swsusp.rst
index 1cf62d80a9ca1..df8eca9e6e363 100644
--- a/Documentation/power/userland-swsusp.rst
+++ b/Documentation/power/userland-swsusp.rst
@@ -115,6 +115,26 @@ SNAPSHOT_S2RAM
to resume the system from RAM if there's enough battery power or restore
its state on the basis of the saved suspend image otherwise)
+SNAPSHOT_ENABLE_ENCRYPTION
+ Enables encryption of the hibernate image within the kernel. Trusted
+ early userspace must write the 32-byte snapshot encryption seed to
+ /sys/power/snapshot_seed before calling this ioctl. Upon suspend
+ (ie when the snapshot device was opened for reading), this ioctl returns
+ an opaque wrapped key blob and the starting nonce. Upon resume (ie when
+ the snapshot device was opened for writing), this ioctl receives the
+ same blob and nonce from usermode after the same seed has been written
+ to /sys/power/snapshot_seed. The plaintext image key is generated and
+ unwrapped inside the kernel.
+
+SNAPSHOT_SET_USER_KEY
+ Mixes additional user key material into the data portion of an encrypted
+ hibernate image. The ioctl argument points to struct uswsusp_user_key.
+ key_len must be between 8 and USWSUSP_USER_KEY_SIZE bytes, and reserved
+ must be zero. During hibernation the kernel writes meta_size with the
+ encrypted metadata size. During resume, userspace passes the saved
+ meta_size back so the kernel can switch keys at the metadata boundary
+ before the snapshot header has been decrypted.
+
The device's read() operation can be used to transfer the snapshot image from
the kernel. It has the following limitations:
diff --git a/block/fops.c b/block/fops.c
index 15783a6180dec..efeb485e91e6c 100644
--- a/block/fops.c
+++ b/block/fops.c
@@ -723,43 +723,65 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
struct file *file = iocb->ki_filp;
struct inode *bd_inode = bdev_file_inode(file);
struct block_device *bdev = I_BDEV(bd_inode);
+ dev_t dev = bd_inode->i_rdev;
bool atomic = iocb->ki_flags & IOCB_ATOMIC;
loff_t size = bdev_nr_bytes(bdev);
+ enum hibernate_snapshot_write hibernate_write;
+ size_t hibernate_len = 0;
size_t shorted = 0;
+ ssize_t hibernate_written = 0;
ssize_t ret;
if (bdev_read_only(bdev))
return -EPERM;
- if (IS_SWAPFILE(bd_inode) && !is_hibernate_resume_dev(bd_inode->i_rdev))
- return -ETXTBSY;
+ if (IS_SWAPFILE(bd_inode) && !is_hibernate_resume_dev(dev)) {
+ if (!is_sync_kiocb(iocb) || !iocb_is_dsync(iocb) ||
+ (iocb->ki_flags & IOCB_DIRECT))
+ return -ETXTBSY;
- if (!iov_iter_count(from))
- return 0;
+ hibernate_len = iov_iter_count(from);
+ hibernate_write = hibernate_snapshot_write_begin(dev, iocb->ki_pos, hibernate_len);
+ if (hibernate_write == HIBERNATE_SNAPSHOT_WRITE_NONE)
+ return -ETXTBSY;
+ } else {
+ hibernate_write = HIBERNATE_SNAPSHOT_WRITE_NONE;
+ }
- if (iocb->ki_pos >= size)
- return -ENOSPC;
+ if (!iov_iter_count(from)) {
+ ret = 0;
+ goto out_hibernate;
+ }
- if ((iocb->ki_flags & (IOCB_NOWAIT | IOCB_DIRECT)) == IOCB_NOWAIT)
- return -EOPNOTSUPP;
+ if (iocb->ki_pos >= size) {
+ ret = -ENOSPC;
+ goto out_hibernate;
+ }
+
+ if ((iocb->ki_flags & (IOCB_NOWAIT | IOCB_DIRECT)) == IOCB_NOWAIT) {
+ ret = -EOPNOTSUPP;
+ goto out_hibernate;
+ }
if (atomic) {
ret = generic_atomic_write_valid(iocb, from);
if (ret)
- return ret;
+ goto out_hibernate;
}
size -= iocb->ki_pos;
if (iov_iter_count(from) > size) {
- if (atomic)
- return -EINVAL;
+ if (atomic) {
+ ret = -EINVAL;
+ goto out_hibernate;
+ }
shorted = iov_iter_count(from) - size;
iov_iter_truncate(from, size);
}
ret = file_update_time(file);
if (ret)
- return ret;
+ goto out_hibernate;
if (iocb->ki_flags & IOCB_DIRECT) {
ret = blkdev_direct_write(iocb, from);
@@ -777,9 +799,16 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
inode_unlock_shared(bd_inode);
}
- if (ret > 0)
+ if (ret > 0) {
+ hibernate_written = ret;
ret = generic_write_sync(iocb, ret);
+ }
iov_iter_reexpand(from, iov_iter_count(from) + shorted);
+
+out_hibernate:
+ if (hibernate_len)
+ hibernate_snapshot_write_end(hibernate_write, hibernate_len,
+ hibernate_written);
return ret;
}
diff --git a/include/linux/suspend.h b/include/linux/suspend.h
index b02876f1ae38a..8308a6583756c 100644
--- a/include/linux/suspend.h
+++ b/include/linux/suspend.h
@@ -426,10 +426,32 @@ static inline bool pm_hibernation_mode_is_suspend(void) { return false; }
int arch_resume_nosmt(void);
+enum hibernate_snapshot_write {
+ HIBERNATE_SNAPSHOT_WRITE_NONE,
+ HIBERNATE_SNAPSHOT_WRITE_IMAGE,
+ HIBERNATE_SNAPSHOT_WRITE_HEADER,
+};
+
#ifdef CONFIG_HIBERNATION_SNAPSHOT_DEV
int is_hibernate_resume_dev(dev_t dev);
+enum hibernate_snapshot_write
+hibernate_snapshot_write_begin(dev_t dev, loff_t pos, size_t count);
+void hibernate_snapshot_write_end(enum hibernate_snapshot_write type,
+ size_t reserved, ssize_t written);
#else
static inline int is_hibernate_resume_dev(dev_t dev) { return 0; }
+
+static inline enum hibernate_snapshot_write
+hibernate_snapshot_write_begin(dev_t dev, loff_t pos, size_t count)
+{
+ return HIBERNATE_SNAPSHOT_WRITE_NONE;
+}
+
+static inline void hibernate_snapshot_write_end(enum hibernate_snapshot_write type,
+ size_t reserved,
+ ssize_t written)
+{
+}
#endif
/* Hibernation and suspend events */
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 8f0f68e245baa..b36685d1f314b 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -439,6 +439,9 @@ extern int find_hibernation_swap_type(dev_t device, sector_t offset);
int find_first_swap(dev_t *device);
extern unsigned int count_swap_pages(int, int);
extern sector_t swapdev_block(int, pgoff_t);
+int swapdev_block_to_offset(int type, sector_t block, pgoff_t *offset);
+int swapdev_block_to_extent(int type, sector_t block, pgoff_t *offset,
+ pgoff_t *nr_pages);
extern int __swap_count(swp_entry_t entry);
extern bool swap_entry_swapped(struct swap_info_struct *si, swp_entry_t entry);
extern int swp_swapcount(swp_entry_t entry);
@@ -535,6 +538,19 @@ static inline int add_swap_extent(struct swap_info_struct *sis,
{
return -EINVAL;
}
+
+static inline int swapdev_block_to_offset(int type, sector_t block,
+ pgoff_t *offset)
+{
+ return -EINVAL;
+}
+
+static inline int swapdev_block_to_extent(int type, sector_t block,
+ pgoff_t *offset,
+ pgoff_t *nr_pages)
+{
+ return -EINVAL;
+}
#endif /* CONFIG_SWAP */
#ifdef CONFIG_MEMCG
static inline int mem_cgroup_swappiness(struct mem_cgroup *memcg)
diff --git a/include/uapi/linux/suspend_ioctls.h b/include/uapi/linux/suspend_ioctls.h
index bcce04e21c0dc..5e23bf936277e 100644
--- a/include/uapi/linux/suspend_ioctls.h
+++ b/include/uapi/linux/suspend_ioctls.h
@@ -13,6 +13,31 @@ struct resume_swap_area {
__u32 dev;
} __attribute__((packed));
+#define USWSUSP_KEY_NONCE_SIZE 16
+#define USWSUSP_USER_KEY_SIZE 32
+
+/*
+ * This structure is used to pass the opaque wrapped hibernate image
+ * encryption key and starting nonce in either direction.
+ */
+struct uswsusp_key_blob {
+ __u32 blob_len;
+ __u8 blob[512];
+ __u8 nonce[USWSUSP_KEY_NONCE_SIZE] __kernel_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; resume passes the saved value in. */
+ __u64 meta_size;
+ __u32 key_len;
+ __u32 reserved;
+ __u8 key[USWSUSP_USER_KEY_SIZE] __kernel_nonstring;
+} __attribute__((packed));
+
#define SNAPSHOT_IOC_MAGIC '3'
#define SNAPSHOT_FREEZE _IO(SNAPSHOT_IOC_MAGIC, 1)
#define SNAPSHOT_UNFREEZE _IO(SNAPSHOT_IOC_MAGIC, 2)
@@ -29,6 +54,10 @@ struct resume_swap_area {
#define SNAPSHOT_PREF_IMAGE_SIZE _IO(SNAPSHOT_IOC_MAGIC, 18)
#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_IOC_MAXNR 20
+#define SNAPSHOT_ENABLE_ENCRYPTION _IOWR(SNAPSHOT_IOC_MAGIC, 21, \
+ struct uswsusp_key_blob)
+#define SNAPSHOT_SET_USER_KEY _IOWR(SNAPSHOT_IOC_MAGIC, 22, \
+ struct uswsusp_user_key)
+#define SNAPSHOT_IOC_MAXNR 22
#endif /* _LINUX_SUSPEND_IOCTLS_H */
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 530c897311d44..a9bee71571cfa 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -115,6 +115,21 @@ config HIBERNATION_DEF_COMP
help
Default compressor to be used for hibernation.
+config ENCRYPTED_HIBERNATION
+ bool "Encryption support for userspace snapshots"
+ depends on HIBERNATION_SNAPSHOT_DEV
+ select CRYPTO_AES
+ select CRYPTO_GCM
+ select CRYPTO_LIB_SHA256
+ help
+ Enable support for kernel-based encryption of hibernation snapshots
+ created by uswsusp tools. A trusted early userspace component must
+ provide the snapshot encryption seed before enabling encryption.
+
+ Say N if userspace handles the image encryption.
+
+ If in doubt, say N.
+
config PM_STD_PARTITION
string "Default resume partition"
depends on HIBERNATION
diff --git a/kernel/power/Makefile b/kernel/power/Makefile
index 773e2789412b9..2fd31b2a250f5 100644
--- a/kernel/power/Makefile
+++ b/kernel/power/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_SUSPEND) += suspend.o
obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o
obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o
obj-$(CONFIG_HIBERNATION_SNAPSHOT_DEV) += user.o
+obj-$(CONFIG_ENCRYPTED_HIBERNATION) += snapenc.o
obj-$(CONFIG_PM_AUTOSLEEP) += autosleep.o
obj-$(CONFIG_PM_WAKELOCKS) += wakelock.o
diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c
index d2479c69d71a4..0b8626bdf81a8 100644
--- a/kernel/power/hibernate.c
+++ b/kernel/power/hibernate.c
@@ -36,6 +36,7 @@
#include <trace/events/power.h>
#include "power.h"
+#include "user.h"
static int nocompress;
@@ -113,6 +114,14 @@ bool hibernation_available(void)
!secretmem_active() && !cxl_mem_active();
}
+bool hibernation_snapshot_dev_available(void)
+{
+ return nohibernate == 0 &&
+ (!security_locked_down(LOCKDOWN_HIBERNATION) ||
+ IS_ENABLED(CONFIG_ENCRYPTED_HIBERNATION)) &&
+ !secretmem_active() && !cxl_mem_active();
+}
+
/**
* hibernation_set_ops - Set the global hibernate operations.
* @ops: Hibernation operations to use in subsequent hibernation transitions.
@@ -1376,12 +1385,35 @@ static ssize_t reserved_size_store(struct kobject *kobj,
power_attr(reserved_size);
+#ifdef CONFIG_ENCRYPTED_HIBERNATION
+static ssize_t snapshot_seed_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ int ret;
+
+ ret = snapshot_store_encryption_seed(buf, n);
+ return ret ? ret : n;
+}
+
+static struct kobj_attribute snapshot_seed_attr = {
+ .attr = {
+ .name = "snapshot_seed",
+ .mode = 0200,
+ },
+ .store = snapshot_seed_store,
+};
+#endif
+
static struct attribute *g[] = {
&disk_attr.attr,
&resume_offset_attr.attr,
&resume_attr.attr,
&image_size_attr.attr,
&reserved_size_attr.attr,
+#ifdef CONFIG_ENCRYPTED_HIBERNATION
+ &snapshot_seed_attr.attr,
+#endif
NULL,
};
diff --git a/kernel/power/power.h b/kernel/power/power.h
index 75b63843886ee..39ef04be283dd 100644
--- a/kernel/power/power.h
+++ b/kernel/power/power.h
@@ -160,6 +160,7 @@ struct snapshot_handle {
extern unsigned int snapshot_additional_pages(struct zone *zone);
extern unsigned long snapshot_get_image_size(void);
+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);
int snapshot_write_finalize(struct snapshot_handle *handle);
@@ -167,8 +168,11 @@ extern int snapshot_image_loaded(struct snapshot_handle *handle);
extern bool hibernate_acquire(void);
extern void hibernate_release(void);
+bool hibernation_snapshot_dev_available(void);
extern sector_t alloc_swapdev_block(int swap);
+bool swsusp_swap_range_allocated(int swap, loff_t pos, size_t count);
+u64 swsusp_swap_map_bytes(u64 image_bytes);
extern void free_all_swap_pages(int swap);
extern int swsusp_swap_in_use(void);
diff --git a/kernel/power/snapenc.c b/kernel/power/snapenc.c
new file mode 100644
index 0000000000000..cd29803ec6c5a
--- /dev/null
+++ b/kernel/power/snapenc.c
@@ -0,0 +1,940 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* This file provides encryption support for system snapshots. */
+
+#include <linux/crypto.h>
+#include <crypto/aead.h>
+#include <crypto/gcm.h>
+#include <crypto/sha2.h>
+#include <crypto/utils.h>
+#include <linux/hex.h>
+#include <linux/mutex.h>
+#include <linux/random.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+
+#include "power.h"
+#include "user.h"
+
+#define SNAPSHOT_SEED_SIZE SHA256_DIGEST_SIZE
+#define SNAPSHOT_KEY_BLOB_VERSION 1
+
+static const u8 snapshot_key_blob_magic[8] = {
+ 'S', 'W', 'S', 'U', 'S', 'P', 'K', '1',
+};
+
+struct snapshot_wrapped_key {
+ u8 magic[sizeof(snapshot_key_blob_magic)];
+ __le32 version;
+ u8 wrap_nonce[GCM_AES_IV_SIZE] __nonstring;
+ u8 encrypted_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring;
+ u8 tag[SNAPSHOT_AUTH_TAG_SIZE] __nonstring;
+} __packed;
+
+static DEFINE_MUTEX(snapshot_seed_mutex);
+static u8 snapshot_seed[SNAPSHOT_SEED_SIZE] __nonstring;
+static bool snapshot_seed_valid;
+
+int snapshot_store_encryption_seed(const char *buf, size_t count)
+{
+ u8 seed[SNAPSHOT_SEED_SIZE] __nonstring;
+ size_t len = count;
+ int ret;
+
+ if (len && buf[len - 1] == '\n')
+ len--;
+ if (len != SNAPSHOT_SEED_SIZE * 2)
+ return -EINVAL;
+
+ ret = hex2bin(seed, buf, sizeof(seed));
+ if (ret)
+ return ret;
+
+ mutex_lock(&snapshot_seed_mutex);
+ if (snapshot_seed_valid) {
+ ret = crypto_memneq(snapshot_seed, seed, sizeof(seed)) ? -EPERM : 0;
+ goto out;
+ }
+
+ memcpy(snapshot_seed, seed, sizeof(seed));
+ snapshot_seed_valid = true;
+ pr_info("PM: hibernate: snapshot encryption seed locked\n");
+
+out:
+ mutex_unlock(&snapshot_seed_mutex);
+ memzero_explicit(seed, sizeof(seed));
+ return ret;
+}
+
+static bool snapshot_copy_encryption_seed(u8 seed[SNAPSHOT_SEED_SIZE])
+{
+ bool valid;
+
+ mutex_lock(&snapshot_seed_mutex);
+ valid = snapshot_seed_valid;
+ if (valid)
+ memcpy(seed, snapshot_seed, SNAPSHOT_SEED_SIZE);
+ mutex_unlock(&snapshot_seed_mutex);
+
+ return valid;
+}
+
+static int snapshot_derive_wrapping_key(u8 key[SNAPSHOT_ENCRYPTION_KEY_SIZE])
+{
+ static const char label[] = "Linux hibernate snapshot key wrap v1";
+ u8 digest[SHA256_DIGEST_SIZE];
+ u8 seed[SNAPSHOT_SEED_SIZE] __nonstring;
+ struct sha256_ctx sha256_ctx;
+
+ if (!snapshot_copy_encryption_seed(seed))
+ return -ENOKEY;
+
+ sha256_init(&sha256_ctx);
+ sha256_update(&sha256_ctx, label, strlen(label));
+ sha256_update(&sha256_ctx, seed, sizeof(seed));
+ sha256_final(&sha256_ctx, digest);
+
+ memcpy(key, digest, SNAPSHOT_ENCRYPTION_KEY_SIZE);
+ memzero_explicit(seed, sizeof(seed));
+ memzero_explicit(digest, sizeof(digest));
+ return 0;
+}
+
+static int snapshot_crypt_wrapped_key(struct snapshot_wrapped_key *wrapped,
+ u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE],
+ bool encrypt)
+{
+ u8 wrap_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring;
+ u8 buf[SNAPSHOT_ENCRYPTION_KEY_SIZE + SNAPSHOT_AUTH_TAG_SIZE] __nonstring;
+ struct crypto_aead *tfm;
+ struct aead_request *req;
+ struct scatterlist sg;
+ DECLARE_CRYPTO_WAIT(wait);
+ int rc;
+
+ rc = snapshot_derive_wrapping_key(wrap_key);
+ if (rc)
+ return rc;
+
+ tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
+ if (IS_ERR(tfm)) {
+ rc = PTR_ERR(tfm);
+ goto out_key;
+ }
+
+ rc = crypto_aead_setkey(tfm, wrap_key, sizeof(wrap_key));
+ if (rc)
+ goto out_tfm;
+
+ rc = crypto_aead_setauthsize(tfm, SNAPSHOT_AUTH_TAG_SIZE);
+ if (rc)
+ goto out_tfm;
+
+ req = aead_request_alloc(tfm, GFP_KERNEL);
+ if (!req) {
+ rc = -ENOMEM;
+ goto out_tfm;
+ }
+
+ if (encrypt) {
+ get_random_bytes(wrapped->wrap_nonce, sizeof(wrapped->wrap_nonce));
+ memcpy(buf, image_key, SNAPSHOT_ENCRYPTION_KEY_SIZE);
+ } else {
+ memcpy(buf, wrapped->encrypted_key, SNAPSHOT_ENCRYPTION_KEY_SIZE);
+ memcpy(buf + SNAPSHOT_ENCRYPTION_KEY_SIZE, wrapped->tag,
+ SNAPSHOT_AUTH_TAG_SIZE);
+ }
+
+ sg_init_one(&sg, buf, sizeof(buf));
+ aead_request_set_callback(req, 0, crypto_req_done, &wait);
+ aead_request_set_ad(req, 0);
+ aead_request_set_crypt(req, &sg, &sg,
+ encrypt ? SNAPSHOT_ENCRYPTION_KEY_SIZE :
+ sizeof(buf),
+ wrapped->wrap_nonce);
+
+ rc = crypto_wait_req(encrypt ? crypto_aead_encrypt(req) :
+ crypto_aead_decrypt(req),
+ &wait);
+ if (rc)
+ goto out_req;
+
+ if (encrypt) {
+ memcpy(wrapped->encrypted_key, buf, SNAPSHOT_ENCRYPTION_KEY_SIZE);
+ memcpy(wrapped->tag, buf + SNAPSHOT_ENCRYPTION_KEY_SIZE,
+ SNAPSHOT_AUTH_TAG_SIZE);
+ } else {
+ memcpy(image_key, buf, SNAPSHOT_ENCRYPTION_KEY_SIZE);
+ }
+
+out_req:
+ aead_request_free(req);
+out_tfm:
+ crypto_free_aead(tfm);
+out_key:
+ memzero_explicit(buf, sizeof(buf));
+ memzero_explicit(wrap_key, sizeof(wrap_key));
+ return rc;
+}
+
+static int snapshot_wrap_image_key(const u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE],
+ struct snapshot_wrapped_key *wrapped)
+{
+ u8 key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring;
+ int rc;
+
+ memset(wrapped, 0, sizeof(*wrapped));
+ memcpy(wrapped->magic, snapshot_key_blob_magic, sizeof(wrapped->magic));
+ wrapped->version = cpu_to_le32(SNAPSHOT_KEY_BLOB_VERSION);
+
+ memcpy(key, image_key, sizeof(key));
+ rc = snapshot_crypt_wrapped_key(wrapped, key, true);
+ memzero_explicit(key, sizeof(key));
+ return rc;
+}
+
+static int snapshot_unwrap_image_key(const struct snapshot_wrapped_key *wrapped,
+ u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE])
+{
+ struct snapshot_wrapped_key tmp;
+ int rc;
+
+ if (memcmp(wrapped->magic, snapshot_key_blob_magic,
+ sizeof(snapshot_key_blob_magic)))
+ return -EINVAL;
+ if (le32_to_cpu(wrapped->version) != SNAPSHOT_KEY_BLOB_VERSION)
+ return -EINVAL;
+
+ tmp = *wrapped;
+ rc = snapshot_crypt_wrapped_key(&tmp, image_key, false);
+ memzero_explicit(&tmp, sizeof(tmp));
+ return rc;
+}
+
+static int snapshot_install_image_key(struct snapshot_data *data,
+ const u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE])
+{
+ int rc;
+
+ memcpy(data->encryption_key, image_key, sizeof(data->encryption_key));
+ rc = crypto_aead_setkey(data->aead_tfm, data->encryption_key,
+ sizeof(data->encryption_key));
+ if (rc)
+ memzero_explicit(data->encryption_key,
+ sizeof(data->encryption_key));
+
+ return rc;
+}
+
+/* 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 sha256_ctx sha256_ctx;
+ int rc;
+
+ /*
+ * 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_ctx);
+
+ sha256_update(&sha256_ctx, data->encryption_key,
+ SNAPSHOT_ENCRYPTION_KEY_SIZE);
+ sha256_update(&sha256_ctx, data->user_key, sizeof(data->user_key));
+ sha256_final(&sha256_ctx, digest);
+
+ BUILD_BUG_ON(SNAPSHOT_ENCRYPTION_KEY_SIZE > SHA256_DIGEST_SIZE);
+
+ rc = crypto_aead_setkey(data->aead_tfm,
+ digest,
+ SNAPSHOT_ENCRYPTION_KEY_SIZE);
+ memzero_explicit(digest, sizeof(digest));
+
+ return rc;
+}
+
+/* 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;
+}
+
+static loff_t snapshot_encrypted_byte_count(loff_t plain_size);
+
+static void snapshot_set_meta_size(struct snapshot_data *data, u64 meta_size)
+{
+ data->meta_size = meta_size;
+ data->crypt_meta_size = snapshot_encrypted_byte_count(meta_size);
+}
+
+static void snapshot_record_encrypted_read(struct snapshot_data *data,
+ size_t count)
+{
+ spin_lock(&data->crypt_lock);
+ data->crypt_bytes_read += count;
+ spin_unlock(&data->crypt_lock);
+}
+
+/* Encrypt more data from the snapshot into the staging area. */
+static int snapshot_encrypt_refill(struct snapshot_data *data)
+{
+ struct aead_request *req = data->aead_req;
+ u8 nonce[GCM_AES_IV_SIZE];
+ DECLARE_CRYPTO_WAIT(wait);
+ size_t total = 0;
+ int pg_idx;
+ int res;
+
+ if (data->crypt_total == 0) {
+ snapshot_set_meta_size(data,
+ 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.
+ */
+ sg_set_buf(&data->sg[0], &data->crypt_total, sizeof(data->crypt_total));
+
+ /* Load the crypt buffer with snapshot pages. */
+ for (pg_idx = 0; pg_idx < CHUNK_SIZE; pg_idx++) {
+ void *buf = data->crypt_pages[pg_idx];
+
+ /* Stop at the meta page boundary before switching keys. */
+ if (data->user_key_valid && total &&
+ ((data->crypt_total + total) == data->meta_size))
+ break;
+
+ res = snapshot_read_next(&data->handle);
+ if (res < 0)
+ return res;
+ if (res == 0)
+ break;
+
+ WARN_ON(res != PAGE_SIZE);
+
+ /*
+ * Copy the page into the staging area. A future optimization
+ * could potentially skip this copy for lowmem pages.
+ */
+ memcpy(buf, data_of(data->handle), PAGE_SIZE);
+ sg_set_buf(&data->sg[1 + pg_idx], buf, PAGE_SIZE);
+ total += PAGE_SIZE;
+ }
+
+ if (!total)
+ return 0;
+
+ sg_set_buf(&data->sg[1 + pg_idx], &data->auth_tag, SNAPSHOT_AUTH_TAG_SIZE);
+ aead_request_set_callback(req, 0, crypto_req_done, &wait);
+ /*
+ * Use incrementing nonces for each chunk, since a 64 bit value won't
+ * roll into re-use for any given hibernate image.
+ */
+ memcpy(&nonce[0], &data->nonce_low, sizeof(data->nonce_low));
+ memcpy(&nonce[sizeof(data->nonce_low)],
+ &data->nonce_high,
+ sizeof(nonce) - sizeof(data->nonce_low));
+
+ data->nonce_low += 1;
+ /* Total does not include AAD or the auth tag. */
+ aead_request_set_crypt(req, data->sg, data->sg, total, nonce);
+ res = crypto_wait_req(crypto_aead_encrypt(req), &wait);
+ if (res)
+ return res;
+
+ data->crypt_size = total;
+ data->crypt_total += total;
+ return 0;
+}
+
+/* Decrypt data from the staging area and push it to the snapshot. */
+static int snapshot_decrypt_drain(struct snapshot_data *data)
+{
+ struct aead_request *req = data->aead_req;
+ u8 nonce[GCM_AES_IV_SIZE];
+ DECLARE_CRYPTO_WAIT(wait);
+ int page_count;
+ size_t total;
+ int pg_idx;
+ int res;
+
+ /* Set up the associated data. */
+ sg_set_buf(&data->sg[0], &data->crypt_total, sizeof(data->crypt_total));
+
+ /*
+ * Get the number of full pages, which could be short at the end. There
+ * should also be a tag at the end, so the offset won't be an even page.
+ */
+ page_count = data->crypt_offset >> PAGE_SHIFT;
+ total = page_count << PAGE_SHIFT;
+ if (total == 0 || total == data->crypt_offset)
+ return -EINVAL;
+
+ /*
+ * Load the sg list with the crypt buffer. Inline decrypt back into the
+ * staging buffer. A future optimization could decrypt directly into
+ * lowmem pages.
+ */
+ for (pg_idx = 0; pg_idx < page_count; pg_idx++)
+ sg_set_buf(&data->sg[1 + pg_idx], data->crypt_pages[pg_idx], PAGE_SIZE);
+
+ /*
+ * 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)) {
+ memcpy(data->auth_tag, data->crypt_pages[pg_idx],
+ sizeof(data->auth_tag));
+ } else if (data->crypt_offset !=
+ ((CHUNK_SIZE << PAGE_SHIFT) + SNAPSHOT_AUTH_TAG_SIZE)) {
+ return -EINVAL;
+ }
+
+ sg_set_buf(&data->sg[1 + pg_idx], &data->auth_tag, SNAPSHOT_AUTH_TAG_SIZE);
+ aead_request_set_callback(req, 0, crypto_req_done, &wait);
+ memcpy(&nonce[0], &data->nonce_low, sizeof(data->nonce_low));
+ memcpy(&nonce[sizeof(data->nonce_low)],
+ &data->nonce_high,
+ sizeof(nonce) - sizeof(data->nonce_low));
+
+ data->nonce_low += 1;
+ aead_request_set_crypt(req, data->sg, data->sg, total + SNAPSHOT_AUTH_TAG_SIZE, nonce);
+ res = crypto_wait_req(crypto_aead_decrypt(req), &wait);
+ if (res)
+ return res;
+
+ data->crypt_size = 0;
+ data->crypt_offset = 0;
+
+ /* Push the decrypted pages further down the stack. */
+ total = 0;
+ for (pg_idx = 0; pg_idx < page_count; pg_idx++) {
+ void *buf = data->crypt_pages[pg_idx];
+
+ res = snapshot_write_next(&data->handle);
+ if (res < 0)
+ return res;
+ if (res == 0)
+ break;
+
+ if (!data_of(data->handle))
+ return -EINVAL;
+
+ WARN_ON(res != PAGE_SIZE);
+
+ /* Copy the decrypted page into the snapshot image. */
+ memcpy(data_of(data->handle), buf, PAGE_SIZE);
+ total += PAGE_SIZE;
+ }
+
+ if (data->crypt_total == 0) {
+ data->meta_size = snapshot_get_meta_page_count() << PAGE_SHIFT;
+ if (data->user_key_valid)
+ data->crypt_meta_size =
+ snapshot_encrypted_byte_count(data->meta_size);
+ }
+
+ data->crypt_total += total;
+ res = snapshot_check_user_key_switch(data);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static ssize_t snapshot_read_next_encrypted(struct snapshot_data *data,
+ void **buf)
+{
+ size_t tag_off;
+
+ /* Refill the encrypted buffer if it's empty. */
+ if (data->crypt_size == 0 ||
+ (data->crypt_offset >=
+ (data->crypt_size + SNAPSHOT_AUTH_TAG_SIZE))) {
+ int rc;
+
+ data->crypt_size = 0;
+ data->crypt_offset = 0;
+ rc = snapshot_encrypt_refill(data);
+ if (rc < 0)
+ return rc;
+ if (!data->crypt_size)
+ return 0;
+ }
+
+ /* Return data pages if the offset is in that region. */
+ if (data->crypt_offset < data->crypt_size) {
+ size_t pg_idx = data->crypt_offset >> PAGE_SHIFT;
+ size_t pg_off = data->crypt_offset & (PAGE_SIZE - 1);
+ *buf = data->crypt_pages[pg_idx] + pg_off;
+ return PAGE_SIZE - pg_off;
+ }
+
+ /* Use offsets just beyond the size to return the tag. */
+ tag_off = data->crypt_offset - data->crypt_size;
+ if (tag_off > SNAPSHOT_AUTH_TAG_SIZE)
+ tag_off = SNAPSHOT_AUTH_TAG_SIZE;
+
+ *buf = data->auth_tag + tag_off;
+ return SNAPSHOT_AUTH_TAG_SIZE - tag_off;
+}
+
+static ssize_t snapshot_write_next_encrypted(struct snapshot_data *data,
+ void **buf)
+{
+ size_t size_avail;
+ size_t tag_off;
+
+ /* Return data pages if the offset is in that region. */
+ 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);
+
+ *buf = data->crypt_pages[pg_idx] + pg_off;
+ size_avail = PAGE_SIZE - pg_off;
+ } else {
+ /* Use offsets just beyond the size to return the tag. */
+ tag_off = data->crypt_offset - (PAGE_SIZE * CHUNK_SIZE);
+ if (tag_off > SNAPSHOT_AUTH_TAG_SIZE)
+ tag_off = SNAPSHOT_AUTH_TAG_SIZE;
+
+ *buf = data->auth_tag + tag_off;
+ size_avail = SNAPSHOT_AUTH_TAG_SIZE - tag_off;
+ }
+
+ if (data->crypt_meta_size &&
+ data->crypt_stream_total < data->crypt_meta_size) {
+ u64 meta_avail = data->crypt_meta_size - data->crypt_stream_total;
+
+ size_avail = min_t(u64, size_avail, meta_avail);
+ }
+
+ return size_avail;
+}
+
+ssize_t snapshot_read_encrypted(struct snapshot_data *data,
+ char __user *buf, size_t count, loff_t *offp)
+{
+ size_t copy_size;
+ size_t not_done;
+ size_t pg_off;
+ void *src;
+ ssize_t src_size;
+
+ if (!count)
+ return 0;
+
+ pg_off = *offp & (PAGE_SIZE - 1);
+ count = min_t(size_t, count, PAGE_SIZE - pg_off);
+
+ src_size = snapshot_read_next_encrypted(data, &src);
+ if (src_size <= 0)
+ return src_size;
+
+ copy_size = min_t(size_t, count, src_size);
+ not_done = copy_to_user(buf, src, copy_size);
+ copy_size -= not_done;
+ if (!copy_size)
+ return -EFAULT;
+
+ data->crypt_offset += copy_size;
+ *offp += copy_size;
+ snapshot_record_encrypted_read(data, copy_size);
+ return copy_size;
+}
+
+ssize_t snapshot_write_encrypted(struct snapshot_data *data,
+ const char __user *buf, size_t count,
+ loff_t *offp)
+{
+ size_t copy_size;
+ size_t not_done;
+ size_t pg_off;
+ void *dst;
+ ssize_t dst_size;
+ int rc;
+
+ if (!count)
+ return 0;
+
+ pg_off = *offp & (PAGE_SIZE - 1);
+ count = min_t(size_t, count, PAGE_SIZE - pg_off);
+
+ dst_size = snapshot_write_next_encrypted(data, &dst);
+ if (dst_size <= 0)
+ return dst_size;
+
+ copy_size = min_t(size_t, count, dst_size);
+ not_done = copy_from_user(dst, buf, copy_size);
+ copy_size -= not_done;
+ if (!copy_size)
+ return -EFAULT;
+
+ data->crypt_offset += copy_size;
+ data->crypt_stream_total += copy_size;
+ /*
+ * Drain the encrypted buffer if it's full, or if we hit the end of the
+ * encrypted metadata and need a key change before user data pages.
+ */
+ if (data->crypt_offset >=
+ (PAGE_SIZE * CHUNK_SIZE) + SNAPSHOT_AUTH_TAG_SIZE ||
+ (data->crypt_meta_size &&
+ data->crypt_stream_total == data->crypt_meta_size)) {
+ rc = snapshot_decrypt_drain(data);
+ if (rc < 0)
+ return rc;
+ }
+
+ *offp += copy_size;
+ return copy_size;
+}
+
+static void snapshot_reset_encryption_state(struct snapshot_data *data)
+{
+ data->crypt_offset = 0;
+ data->crypt_size = 0;
+ data->crypt_total = 0;
+ data->crypt_stream_total = 0;
+ data->nonce_low = 0;
+ data->nonce_high = 0;
+ data->meta_size = 0;
+ data->crypt_meta_size = 0;
+ data->user_key_valid = false;
+ spin_lock(&data->crypt_lock);
+ data->crypt_bytes_read = 0;
+ data->crypt_swap_reserved = 0;
+ data->crypt_header_reserved = 0;
+ spin_unlock(&data->crypt_lock);
+ memset(data->auth_tag, 0, sizeof(data->auth_tag));
+}
+
+void snapshot_teardown_encryption(struct snapshot_data *data)
+{
+ int i;
+
+ if (data->aead_req) {
+ aead_request_free(data->aead_req);
+ data->aead_req = NULL;
+ }
+
+ if (data->aead_tfm) {
+ crypto_free_aead(data->aead_tfm);
+ data->aead_tfm = NULL;
+ }
+
+ for (i = 0; i < CHUNK_SIZE; i++) {
+ if (data->crypt_pages[i]) {
+ free_page((unsigned long)data->crypt_pages[i]);
+ data->crypt_pages[i] = NULL;
+ }
+ }
+
+ memzero_explicit(data->encryption_key, sizeof(data->encryption_key));
+ memzero_explicit(data->user_key, sizeof(data->user_key));
+ snapshot_reset_encryption_state(data);
+}
+
+static int snapshot_setup_encryption_common(struct snapshot_data *data)
+{
+ int i, rc;
+
+ /* This only works once per hibernate. */
+ if (data->aead_tfm)
+ return -EINVAL;
+
+ snapshot_reset_encryption_state(data);
+ memset(data->crypt_pages, 0, sizeof(data->crypt_pages));
+
+ /* Set up the encryption transform */
+ data->aead_tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
+ if (IS_ERR(data->aead_tfm)) {
+ rc = PTR_ERR(data->aead_tfm);
+ data->aead_tfm = NULL;
+ return rc;
+ }
+
+ rc = -ENOMEM;
+ data->aead_req = aead_request_alloc(data->aead_tfm, GFP_KERNEL);
+ if (!data->aead_req)
+ goto setup_fail;
+
+ /* Allocate the staging area */
+ for (i = 0; i < CHUNK_SIZE; i++) {
+ data->crypt_pages[i] = (void *)__get_free_page(GFP_KERNEL);
+ if (!data->crypt_pages[i])
+ goto setup_fail;
+ }
+
+ sg_init_table(data->sg, CHUNK_SIZE + 2);
+
+ /*
+ * The associated data will be the offset so that blocks can't be
+ * rearranged.
+ */
+ aead_request_set_ad(data->aead_req, sizeof(data->crypt_total));
+ rc = crypto_aead_setauthsize(data->aead_tfm, SNAPSHOT_AUTH_TAG_SIZE);
+ if (rc)
+ goto setup_fail;
+
+ return 0;
+
+setup_fail:
+ snapshot_teardown_encryption(data);
+ return rc;
+}
+
+int snapshot_get_encryption_key(struct snapshot_data *data,
+ struct uswsusp_key_blob __user *key)
+{
+ struct snapshot_wrapped_key wrapped = {};
+ u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring = {};
+ u8 nonce[USWSUSP_KEY_NONCE_SIZE];
+ int rc;
+
+ /* Don't pull a random key from a world that can be reset. */
+ if (data->ready)
+ return -EPIPE;
+
+ rc = snapshot_setup_encryption_common(data);
+ if (rc)
+ return rc;
+
+ /* Build a random starting nonce. */
+ get_random_bytes(nonce, sizeof(nonce));
+ memcpy(&data->nonce_low, &nonce[0], sizeof(data->nonce_low));
+ memcpy(&data->nonce_high, &nonce[8], sizeof(data->nonce_high));
+
+ /* Build and install a random image encryption key. */
+ get_random_bytes(image_key, sizeof(image_key));
+ rc = snapshot_install_image_key(data, image_key);
+ if (rc)
+ goto fail;
+
+ rc = snapshot_wrap_image_key(image_key, &wrapped);
+ if (rc)
+ goto fail;
+
+ /* Hand the wrapped key and clear nonce back to user mode. */
+ rc = put_user(sizeof(wrapped), &key->blob_len);
+ if (rc)
+ goto fail;
+
+ BUILD_BUG_ON(sizeof(wrapped) >
+ sizeof(((struct uswsusp_key_blob *)0)->blob));
+ if (copy_to_user(&key->blob, &wrapped, sizeof(wrapped))) {
+ rc = -EFAULT;
+ goto fail;
+ }
+
+ if (copy_to_user(&key->nonce, &nonce, sizeof(nonce))) {
+ rc = -EFAULT;
+ goto fail;
+ }
+
+ memzero_explicit(image_key, sizeof(image_key));
+ memzero_explicit(&wrapped, sizeof(wrapped));
+ return 0;
+
+fail:
+ memzero_explicit(image_key, sizeof(image_key));
+ memzero_explicit(&wrapped, sizeof(wrapped));
+ snapshot_teardown_encryption(data);
+ return rc;
+}
+
+int snapshot_set_encryption_key(struct snapshot_data *data,
+ struct uswsusp_key_blob __user *key)
+{
+ struct uswsusp_key_blob *blob;
+ struct snapshot_wrapped_key wrapped = {};
+ u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring = {};
+ int rc;
+
+ /* It's too late if data's been pushed in. */
+ if (data->handle.cur)
+ return -EPIPE;
+
+ rc = snapshot_setup_encryption_common(data);
+ if (rc)
+ return rc;
+
+ /* Load the key from user mode. */
+ blob = memdup_user(key, sizeof(*blob));
+ if (IS_ERR(blob)) {
+ rc = PTR_ERR(blob);
+ goto crypto_setup_fail;
+ }
+
+ if (blob->blob_len != sizeof(wrapped)) {
+ rc = -EINVAL;
+ goto out_blob;
+ }
+
+ memcpy(&wrapped, blob->blob, sizeof(wrapped));
+ rc = snapshot_unwrap_image_key(&wrapped, image_key);
+ if (rc)
+ goto out_blob;
+
+ rc = snapshot_install_image_key(data, image_key);
+ if (rc)
+ goto out_blob;
+
+ /* Load the starting nonce. */
+ memcpy(&data->nonce_low, &blob->nonce[0], sizeof(data->nonce_low));
+ memcpy(&data->nonce_high, &blob->nonce[8], sizeof(data->nonce_high));
+
+out_blob:
+ kfree_sensitive(blob);
+ memzero_explicit(&wrapped, sizeof(wrapped));
+ memzero_explicit(image_key, sizeof(image_key));
+ if (rc)
+ goto crypto_setup_fail;
+
+ return 0;
+
+crypto_setup_fail:
+ memzero_explicit(&wrapped, sizeof(wrapped));
+ memzero_explicit(image_key, sizeof(image_key));
+ snapshot_teardown_encryption(data);
+ return rc;
+}
+
+static loff_t snapshot_encrypted_byte_count(loff_t plain_size)
+{
+ 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 plain_size + (chunks * SNAPSHOT_AUTH_TAG_SIZE);
+}
+
+static loff_t snapshot_encrypted_split_byte_count(loff_t raw_size,
+ loff_t meta_plain_size)
+{
+ if (raw_size <= meta_plain_size)
+ return snapshot_encrypted_byte_count(raw_size);
+
+ return snapshot_encrypted_byte_count(meta_plain_size) +
+ snapshot_encrypted_byte_count(raw_size - meta_plain_size);
+}
+
+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;
+ u64 size;
+ int rc;
+
+ if (!snapshot_encryption_enabled(data))
+ return -EINVAL;
+
+ if (copy_from_user(&user_key, key, sizeof(struct uswsusp_user_key)))
+ return -EFAULT;
+
+ BUILD_BUG_ON(sizeof(data->user_key) < sizeof(user_key.key));
+ rc = -EINVAL;
+ if (user_key.reserved)
+ goto out;
+ if (user_key.key_len > sizeof(data->user_key))
+ goto out;
+ if (user_key.key_len < 8)
+ goto out;
+
+ if (data->mode == O_RDONLY && !data->ready) {
+ rc = -ENODATA;
+ goto out;
+ }
+
+ /*
+ * Return the metadata size, the number of bytes that can be fed in before
+ * the user data key is needed at resume time.
+ */
+ if (data->mode == O_WRONLY && !data->meta_size) {
+ if (user_key.meta_size < PAGE_SIZE + SNAPSHOT_AUTH_TAG_SIZE)
+ goto out;
+
+ data->crypt_meta_size = user_key.meta_size;
+ size = data->crypt_meta_size;
+ } else {
+ snapshot_set_meta_size(data,
+ snapshot_get_meta_page_count() << PAGE_SHIFT);
+ size = data->crypt_meta_size;
+ }
+
+ rc = put_user(size, &key->meta_size);
+ if (rc)
+ goto out;
+
+ key_len = user_key.key_len;
+
+ /* Don't allow it if it's too late. */
+ if ((data->meta_size && data->crypt_total > data->meta_size) ||
+ (data->crypt_meta_size &&
+ data->crypt_stream_total > data->crypt_meta_size)) {
+ rc = -EBUSY;
+ goto out;
+ }
+
+ 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)
+ goto out;
+
+ rc = 0;
+
+out:
+ memzero_explicit(&user_key, sizeof(user_key));
+ return rc;
+}
+
+loff_t snapshot_get_encrypted_image_size(struct snapshot_data *data,
+ loff_t raw_size)
+{
+ loff_t meta_plain_size = data->meta_size;
+ loff_t split_size;
+
+ if (!meta_plain_size)
+ meta_plain_size = snapshot_get_meta_page_count() << PAGE_SHIFT;
+
+ split_size = snapshot_encrypted_split_byte_count(raw_size,
+ meta_plain_size);
+ if (!data->user_key_valid)
+ return max(snapshot_encrypted_byte_count(raw_size), split_size);
+
+ return split_size;
+}
+
+int snapshot_finalize_decrypted_image(struct snapshot_data *data)
+{
+ int rc;
+
+ if (data->crypt_offset != 0) {
+ rc = snapshot_decrypt_drain(data);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index d933b5b2c05d4..02400ac125c58 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -2177,6 +2177,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));
diff --git a/kernel/power/swap.c b/kernel/power/swap.c
index c626e9dc3c1c1..3bdd58d5b62cb 100644
--- a/kernel/power/swap.c
+++ b/kernel/power/swap.c
@@ -166,6 +166,104 @@ static int swsusp_extents_insert(unsigned long swap_offset)
return 0;
}
+static bool swsusp_extents_contain_range(unsigned long swap_offset,
+ unsigned long nr_pages)
+{
+ struct rb_node *node = swsusp_extents.rb_node;
+ struct swsusp_extent *ext;
+ unsigned long end;
+
+ if (!nr_pages)
+ return false;
+
+ end = swap_offset + nr_pages - 1;
+ if (end < swap_offset)
+ return false;
+
+ while (node) {
+ ext = rb_entry(node, struct swsusp_extent, node);
+ if (swap_offset < ext->start)
+ node = node->rb_left;
+ else if (swap_offset > ext->end)
+ node = node->rb_right;
+ else
+ goto found;
+ }
+
+ return false;
+
+found:
+ for (;;) {
+ if (swap_offset < ext->start)
+ return false;
+ if (end <= ext->end)
+ return true;
+ if (ext->end == (unsigned long)-1)
+ return false;
+
+ swap_offset = ext->end + 1;
+ node = rb_next(&ext->node);
+ if (!node)
+ return false;
+
+ ext = rb_entry(node, struct swsusp_extent, node);
+ }
+}
+
+bool swsusp_swap_range_allocated(int swap, loff_t pos, size_t count)
+{
+ u64 block;
+ u64 end;
+ u64 end_block;
+
+ if (swap < 0 || pos < 0 || !count)
+ return false;
+
+ end = (u64)pos + count - 1;
+ if (end < (u64)pos)
+ return false;
+
+ block = (u64)pos >> PAGE_SHIFT;
+ end_block = end >> PAGE_SHIFT;
+
+ for (;;) {
+ u64 remaining_pages = end_block - block + 1;
+ pgoff_t swap_offset;
+ pgoff_t mapped_pages;
+ sector_t page_block = block;
+ u64 pages;
+
+ if (!remaining_pages)
+ return false;
+ if ((u64)page_block != block)
+ return false;
+ if (swapdev_block_to_extent(swap, page_block, &swap_offset,
+ &mapped_pages))
+ return false;
+ if (!mapped_pages)
+ return false;
+ pages = min_t(u64, mapped_pages, remaining_pages);
+ if ((u64)(unsigned long)pages != pages)
+ return false;
+ if (!swsusp_extents_contain_range(swap_offset, pages))
+ return false;
+ if (pages == remaining_pages)
+ return true;
+ block += pages;
+ }
+}
+
+u64 swsusp_swap_map_bytes(u64 image_bytes)
+{
+ u64 image_pages = DIV_ROUND_UP_ULL(image_bytes, PAGE_SIZE);
+ u64 map_pages = image_pages / MAP_PAGE_ENTRIES + 1;
+
+ if (map_pages > U64_MAX >> PAGE_SHIFT)
+ return U64_MAX;
+
+ return map_pages << PAGE_SHIFT;
+}
+
sector_t alloc_swapdev_block(int swap)
{
unsigned long offset;
diff --git a/kernel/power/user.c b/kernel/power/user.c
index d0fcfba7ac235..57b8ce83a626f 100644
--- a/kernel/power/user.c
+++ b/kernel/power/user.c
@@ -21,54 +21,185 @@
#include <linux/console.h>
#include <linux/cpu.h>
#include <linux/freezer.h>
+#include <linux/pid.h>
+#include <linux/sched/signal.h>
+#include <linux/security.h>
#include <linux/uaccess.h>
#include "power.h"
+#include "user.h"
static bool need_wait;
-
-static struct snapshot_data {
- struct snapshot_handle handle;
- int swap;
- int mode;
- bool frozen;
- bool ready;
- bool platform_support;
- bool free_bitmaps;
- dev_t dev;
-} snapshot_state;
+struct snapshot_data snapshot_state;
int is_hibernate_resume_dev(dev_t dev)
{
return hibernation_available() && snapshot_state.dev == dev;
}
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+static bool snapshot_encrypted_output_active(struct snapshot_data *data, dev_t dev)
+{
+ return data->encryption_required && data->mode == O_RDONLY &&
+ data->ready && data->dev == dev &&
+ data->owner_tgid == task_tgid(current) &&
+ snapshot_encryption_enabled(data);
+}
+
+static bool snapshot_header_write_range(struct snapshot_data *data,
+ loff_t pos, size_t count)
+{
+ loff_t header_offset = data->swap_header_offset;
+ loff_t offset;
+
+ if (pos < header_offset)
+ return false;
+
+ offset = pos - header_offset;
+ return offset < PAGE_SIZE && count <= PAGE_SIZE - offset;
+}
+
+static u64 snapshot_swap_write_budget(struct snapshot_data *data)
+{
+ u64 image_bytes = round_up(data->crypt_bytes_read, PAGE_SIZE);
+ u64 map_bytes = swsusp_swap_map_bytes(data->crypt_bytes_read);
+ u64 budget = image_bytes + map_bytes;
+
+ if (image_bytes < data->crypt_bytes_read || budget < image_bytes)
+ return U64_MAX;
+
+ return budget;
+}
+
+static void snapshot_reset_swap_write_reservation(struct snapshot_data *data)
+{
+ spin_lock(&data->crypt_lock);
+ data->crypt_swap_reserved = 0;
+ spin_unlock(&data->crypt_lock);
+}
+#endif
+
+enum hibernate_snapshot_write
+hibernate_snapshot_write_begin(dev_t dev, loff_t pos, size_t count)
+{
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+ struct snapshot_data *data = &snapshot_state;
+ enum hibernate_snapshot_write type = HIBERNATE_SNAPSHOT_WRITE_NONE;
+ bool image_range_allocated;
+ u64 swap_budget;
+
+ if (!count || !snapshot_encrypted_output_active(data, dev))
+ return HIBERNATE_SNAPSHOT_WRITE_NONE;
+
+ mutex_lock(&system_transition_mutex);
+
+ if (!snapshot_encrypted_output_active(data, dev))
+ goto unlock;
+
+ image_range_allocated = swsusp_swap_range_allocated(data->swap, pos, count);
+
+ spin_lock(&data->crypt_lock);
+ swap_budget = snapshot_swap_write_budget(data);
+ if (snapshot_header_write_range(data, pos, count) &&
+ data->crypt_header_reserved <= PAGE_SIZE &&
+ PAGE_SIZE - data->crypt_header_reserved >= count) {
+ data->crypt_header_reserved += count;
+ type = HIBERNATE_SNAPSHOT_WRITE_HEADER;
+ } else if (image_range_allocated &&
+ swap_budget >= data->crypt_swap_reserved &&
+ swap_budget - data->crypt_swap_reserved >= count) {
+ data->crypt_swap_reserved += count;
+ type = HIBERNATE_SNAPSHOT_WRITE_IMAGE;
+ }
+ spin_unlock(&data->crypt_lock);
+
+ if (type == HIBERNATE_SNAPSHOT_WRITE_NONE)
+ goto unlock;
+
+ return type;
+
+unlock:
+ mutex_unlock(&system_transition_mutex);
+ return HIBERNATE_SNAPSHOT_WRITE_NONE;
+#else
+ return HIBERNATE_SNAPSHOT_WRITE_NONE;
+#endif
+}
+
+void hibernate_snapshot_write_end(enum hibernate_snapshot_write type,
+ size_t reserved, ssize_t written)
+{
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+ struct snapshot_data *data = &snapshot_state;
+ u64 *reserved_total;
+ size_t unused;
+
+ if (type == HIBERNATE_SNAPSHOT_WRITE_NONE)
+ return;
+ if (!reserved)
+ goto unlock;
+
+ if (type == HIBERNATE_SNAPSHOT_WRITE_HEADER)
+ unused = reserved;
+ else if (written <= 0)
+ unused = reserved;
+ else if (written < reserved)
+ unused = reserved - written;
+ else
+ goto unlock;
+
+ reserved_total = type == HIBERNATE_SNAPSHOT_WRITE_HEADER ?
+ &data->crypt_header_reserved : &data->crypt_swap_reserved;
+
+ spin_lock(&data->crypt_lock);
+ *reserved_total -= min_t(u64, *reserved_total, unused);
+ spin_unlock(&data->crypt_lock);
+
+unlock:
+ mutex_unlock(&system_transition_mutex);
+#endif
+}
+
+static bool snapshot_encryption_required(struct snapshot_data *data)
+{
+ data->encryption_required = security_locked_down(LOCKDOWN_HIBERNATION);
+ return data->encryption_required;
+}
+
static int snapshot_open(struct inode *inode, struct file *filp)
{
struct snapshot_data *data;
unsigned int sleep_flags;
int error;
- if (!hibernation_available())
+ if (!hibernation_snapshot_dev_available())
return -EPERM;
sleep_flags = lock_system_sleep();
if (!hibernate_acquire()) {
error = -EBUSY;
- goto Unlock;
+ goto unlock;
}
if ((filp->f_flags & O_ACCMODE) == O_RDWR) {
hibernate_release();
error = -ENOSYS;
- goto Unlock;
+ goto unlock;
}
nonseekable_open(inode, filp);
data = &snapshot_state;
filp->private_data = data;
memset(&data->handle, 0, sizeof(struct snapshot_handle));
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+ spin_lock_init(&data->crypt_lock);
+ data->crypt_bytes_read = 0;
+ data->crypt_swap_reserved = 0;
+ data->crypt_header_reserved = 0;
+ data->swap_header_offset = 0;
+ data->owner_tgid = NULL;
+#endif
if ((filp->f_flags & O_ACCMODE) == O_RDONLY) {
/* Hibernating. The image device should be accessible. */
data->swap = pin_hibernation_swap_type(swsusp_resume_device, 0);
@@ -99,8 +230,13 @@ static int snapshot_open(struct inode *inode, struct file *filp)
data->ready = false;
data->platform_support = false;
data->dev = 0;
+ data->encryption_required = snapshot_encryption_required(data);
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+ if (!error)
+ data->owner_tgid = get_task_pid(current, PIDTYPE_TGID);
+#endif
- Unlock:
+ unlock:
unlock_system_sleep(sleep_flags);
return error;
@@ -116,6 +252,10 @@ static int snapshot_release(struct inode *inode, struct file *filp)
swsusp_free();
data = filp->private_data;
data->dev = 0;
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+ put_pid(data->owner_tgid);
+ data->owner_tgid = NULL;
+#endif
free_all_swap_pages(data->swap);
unpin_hibernation_swap_type(data->swap);
if (data->frozen) {
@@ -125,6 +265,7 @@ static int snapshot_release(struct inode *inode, struct file *filp)
} else if (data->free_bitmaps) {
free_basic_memory_bitmaps();
}
+ snapshot_teardown_encryption(data);
pm_notifier_call_chain(data->mode == O_RDONLY ?
PM_POST_HIBERNATION : PM_POST_RESTORE);
hibernate_release();
@@ -147,12 +288,23 @@ static ssize_t snapshot_read(struct file *filp, char __user *buf,
data = filp->private_data;
if (!data->ready) {
res = -ENODATA;
- goto Unlock;
+ goto unlock;
+ }
+ if (snapshot_encryption_required(data) &&
+ !snapshot_encryption_enabled(data)) {
+ res = -EPERM;
+ goto unlock;
+ }
+
+ if (snapshot_encryption_enabled(data)) {
+ res = snapshot_read_encrypted(data, buf, count, offp);
+ goto unlock;
}
+
if (!pg_offp) { /* on page boundary? */
res = snapshot_read_next(&data->handle);
if (res <= 0)
- goto Unlock;
+ goto unlock;
} else {
res = PAGE_SIZE - pg_offp;
}
@@ -162,7 +314,7 @@ static ssize_t snapshot_read(struct file *filp, char __user *buf,
if (res > 0)
*offp += res;
- Unlock:
+ unlock:
unlock_system_sleep(sleep_flags);
return res;
@@ -185,6 +337,17 @@ static ssize_t snapshot_write(struct file *filp, const char __user *buf,
data = filp->private_data;
+ if (snapshot_encryption_required(data) &&
+ !snapshot_encryption_enabled(data)) {
+ res = -EPERM;
+ goto unlock;
+ }
+
+ if (snapshot_encryption_enabled(data)) {
+ res = snapshot_write_encrypted(data, buf, count, offp);
+ goto unlock;
+ }
+
if (!pg_offp) {
res = snapshot_write_next(&data->handle);
if (res <= 0)
@@ -252,6 +415,9 @@ static int snapshot_set_swap_area(struct snapshot_data *data,
if (data->swap < 0)
return swdev ? -ENODEV : -EINVAL;
data->dev = swdev;
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+ data->swap_header_offset = (loff_t)offset << PAGE_SHIFT;
+#endif
return 0;
}
@@ -318,6 +484,11 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
error = -EPERM;
break;
}
+ if (snapshot_encryption_required(data) &&
+ !snapshot_encryption_enabled(data)) {
+ error = -EPERM;
+ break;
+ }
pm_restore_gfp_mask();
error = hibernation_snapshot(data->platform_support);
if (!error) {
@@ -328,6 +499,17 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
break;
case SNAPSHOT_ATOMIC_RESTORE:
+ if (snapshot_encryption_required(data) &&
+ !snapshot_encryption_enabled(data)) {
+ error = -EPERM;
+ break;
+ }
+ if (snapshot_encryption_enabled(data)) {
+ error = snapshot_finalize_decrypted_image(data);
+ if (error)
+ break;
+ }
+
error = snapshot_write_finalize(&data->handle);
if (error)
break;
@@ -346,6 +528,7 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
swsusp_free();
memset(&data->handle, 0, sizeof(struct snapshot_handle));
data->ready = false;
+ snapshot_teardown_encryption(data);
/*
* It is necessary to thaw kernel threads here, because
* SNAPSHOT_CREATE_IMAGE may be invoked directly after
@@ -368,6 +551,8 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
}
size = snapshot_get_image_size();
size <<= PAGE_SHIFT;
+ if (snapshot_encryption_enabled(data))
+ size = snapshot_get_encrypted_image_size(data, size);
error = put_user(size, (loff_t __user *)arg);
break;
@@ -397,6 +582,9 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
break;
}
free_all_swap_pages(data->swap);
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+ snapshot_reset_swap_write_reservation(data);
+#endif
break;
case SNAPSHOT_S2RAM:
@@ -404,6 +592,11 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
error = -EPERM;
break;
}
+ if (snapshot_encryption_required(data) &&
+ !snapshot_encryption_enabled(data)) {
+ error = -EPERM;
+ break;
+ }
/*
* Tasks are frozen and the notifiers have been called with
* PM_HIBERNATION_PREPARE
@@ -417,6 +610,11 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
break;
case SNAPSHOT_POWER_OFF:
+ if (snapshot_encryption_required(data) &&
+ !snapshot_encryption_enabled(data)) {
+ error = -EPERM;
+ break;
+ }
if (data->platform_support)
error = hibernation_platform_enter();
break;
@@ -425,6 +623,17 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
error = snapshot_set_swap_area(data, (void __user *)arg);
break;
+ case SNAPSHOT_ENABLE_ENCRYPTION:
+ if (data->mode == O_RDONLY)
+ error = snapshot_get_encryption_key(data, (void __user *)arg);
+ else
+ 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;
@@ -448,6 +657,8 @@ snapshot_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
case SNAPSHOT_ALLOC_SWAP_PAGE:
case SNAPSHOT_CREATE_IMAGE:
case SNAPSHOT_SET_SWAP_AREA:
+ case SNAPSHOT_ENABLE_ENCRYPTION:
+ case SNAPSHOT_SET_USER_KEY:
return snapshot_ioctl(file, cmd,
(unsigned long) compat_ptr(arg));
default:
diff --git a/kernel/power/user.h b/kernel/power/user.h
new file mode 100644
index 0000000000000..4df2f9ae9ada4
--- /dev/null
+++ b/kernel/power/user.h
@@ -0,0 +1,151 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/crypto.h>
+#include <linux/scatterlist.h>
+#include <linux/spinlock.h>
+#include <linux/suspend_ioctls.h>
+#include <crypto/aead.h>
+#include <crypto/aes.h>
+
+struct pid;
+
+#define SNAPSHOT_ENCRYPTION_KEY_SIZE AES_KEYSIZE_128
+#define SNAPSHOT_AUTH_TAG_SIZE 16
+
+/* Define the number of pages in a single AEAD encryption chunk. */
+#define CHUNK_SIZE 16
+
+struct snapshot_data {
+ struct snapshot_handle handle;
+ int swap;
+ int mode;
+ bool frozen;
+ bool ready;
+ bool platform_support;
+ bool free_bitmaps;
+ bool encryption_required;
+ dev_t dev;
+
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+ struct crypto_aead *aead_tfm;
+ struct aead_request *aead_req;
+ void *crypt_pages[CHUNK_SIZE];
+ u8 auth_tag[SNAPSHOT_AUTH_TAG_SIZE];
+ struct scatterlist sg[CHUNK_SIZE + 2]; /* Add room for AD and auth tag. */
+ size_t crypt_offset;
+ size_t crypt_size;
+ u64 crypt_total;
+ u64 crypt_stream_total;
+ u64 nonce_low;
+ u64 nonce_high;
+ u8 encryption_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring;
+ u8 user_key[USWSUSP_USER_KEY_SIZE] __nonstring;
+ bool user_key_valid;
+ u64 meta_size;
+ u64 crypt_meta_size;
+ /*
+ * Protects crypt_bytes_read and the reservation counters below.
+ * crypt_swap_reserved covers encrypted snapshot bytes and userspace
+ * generated swap-map pages.
+ */
+ spinlock_t crypt_lock;
+ u64 crypt_bytes_read;
+ u64 crypt_swap_reserved;
+ u64 crypt_header_reserved;
+ loff_t swap_header_offset;
+ struct pid *owner_tgid;
+#endif
+
+};
+
+extern struct snapshot_data snapshot_state;
+
+/* kernel/power/snapenc.c routines */
+#if defined(CONFIG_ENCRYPTED_HIBERNATION)
+
+ssize_t snapshot_read_encrypted(struct snapshot_data *data,
+ char __user *buf, size_t count, loff_t *offp);
+
+ssize_t snapshot_write_encrypted(struct snapshot_data *data,
+ const char __user *buf, size_t count,
+ loff_t *offp);
+
+void snapshot_teardown_encryption(struct snapshot_data *data);
+int snapshot_get_encryption_key(struct snapshot_data *data,
+ struct uswsusp_key_blob __user *key);
+
+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);
+
+int snapshot_store_encryption_seed(const char *buf, size_t count);
+
+loff_t snapshot_get_encrypted_image_size(struct snapshot_data *data,
+ loff_t raw_size);
+
+int snapshot_finalize_decrypted_image(struct snapshot_data *data);
+
+static inline bool snapshot_encryption_enabled(struct snapshot_data *data)
+{
+ return data->aead_tfm;
+}
+
+#else
+
+static inline ssize_t snapshot_read_encrypted(struct snapshot_data *data,
+ char __user *buf, size_t count,
+ loff_t *offp)
+{
+ return -ENOTTY;
+}
+
+static inline ssize_t snapshot_write_encrypted(struct snapshot_data *data,
+ const char __user *buf,
+ size_t count, loff_t *offp)
+{
+ return -ENOTTY;
+}
+
+static inline void snapshot_teardown_encryption(struct snapshot_data *data) {}
+static inline int snapshot_get_encryption_key(struct snapshot_data *data,
+ struct uswsusp_key_blob __user *key)
+{
+ return -ENOTTY;
+}
+
+static inline int snapshot_set_encryption_key(struct snapshot_data *data,
+ struct uswsusp_key_blob __user *key)
+{
+ return -ENOTTY;
+}
+
+static inline int snapshot_set_user_key(struct snapshot_data *data,
+ struct uswsusp_user_key __user *key)
+{
+ return -ENOTTY;
+}
+
+static inline int snapshot_store_encryption_seed(const char *buf, size_t count)
+{
+ return -ENOTTY;
+}
+
+static inline loff_t snapshot_get_encrypted_image_size(struct snapshot_data *data,
+ loff_t raw_size)
+{
+ return raw_size;
+}
+
+static inline int snapshot_finalize_decrypted_image(struct snapshot_data *data)
+{
+ return -ENOTTY;
+}
+
+static inline bool snapshot_encryption_enabled(struct snapshot_data *data)
+{
+ return false;
+}
+
+#endif
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 78b49b0658add..11df0c556f4e9 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -2366,6 +2366,38 @@ sector_t swapdev_block(int type, pgoff_t offset)
return se->start_block + (offset - se->start_page);
}
+int swapdev_block_to_extent(int type, sector_t block, pgoff_t *offset,
+ pgoff_t *nr_pages)
+{
+ struct swap_info_struct *si = swap_type_to_info(type);
+ struct swap_extent *se;
+ struct rb_node *rb;
+
+ if (!si || !(si->flags & SWP_WRITEOK))
+ return -ENODEV;
+
+ for (rb = rb_first(&si->swap_extent_root); rb; rb = rb_next(rb)) {
+ se = rb_entry(rb, struct swap_extent, rb_node);
+ if (block >= se->start_block &&
+ block - se->start_block < se->nr_pages) {
+ pgoff_t page = block - se->start_block;
+
+ *offset = se->start_page + page;
+ *nr_pages = se->nr_pages - page;
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+int swapdev_block_to_offset(int type, sector_t block, pgoff_t *offset)
+{
+ pgoff_t nr_pages;
+
+ return swapdev_block_to_extent(type, block, offset, &nr_pages);
+}
+
/*
* Return either the total number of swap pages of given type, or the number
* of free pages of that type (depending on @free)
|
| 3/2 | 2026/08/04 12:12 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24811 output: 263 thoughts: 1516 Thoughts: **Encryption for Userspace Hibernation Snapshots - Key Areas for Investigation** Okay, so I've got this patch series that introduces encryption support for userspace hibernation snapshots. My initial assessment is that it's a pretty substantial change and definitely warrants a good fuzzing campaign. It touches quite a few key areas of the kernel. First, the core of it is the encryption itself. I'm seeing changes in several files: `block/fops.c`, various header files (`suspend.h`, `swap.h`, `suspend_ioctls.h`), the kernel power config files (`Kconfig`, `Makefile`, `power.h`), and crucially, files like `hibernate.c`, `snapenc.c`, `snapshot.c`, `swap.c`, and `user.c` within the `kernel/power` directory, along with `mm/swapfile.c`. The gist of it is enabling kernel-based encryption. They've added some new ioctls to `/dev/snapshot`: `SNAPSHOT_ENABLE_ENCRYPTION` and `SNAPSHOT_SET_USER_KEY`. There's also a new sysfs file, `/sys/power/snapshot_seed`. Let's break this down. From a configuration standpoint, the new `CONFIG_ENCRYPTED_HIBERNATION` option is the key one. It looks like it depends on `HIBERNATION_SNAPSHOT_DEV`, which itself is the config for `/dev/snapshot`. Of course, we'll need `HIBERNATION` as well. Now, let's zero in on the interesting functions. In `kernel/power/user.c`, `snapshot_ioctl` is going to handle those new ioctls. `kernel/power/snapenc.c` is going to be the heart of the encryption/decryption logic. Specifically, I'll be looking at: `snapshot_get_encryption_key`, `snapshot_set_encryption_key`, `snapshot_set_user_key`, `snapshot_read_encrypted`, `snapshot_write_encrypted`, `snapshot_store_encryption_seed`, and `snapshot_finalize_decrypted_image`. In `kernel/power/user.c`, `hibernate_snapshot_write_begin` and `hibernate_snapshot_write_end` are important for understanding the snapshot write process. Then, over in `mm/swapfile.c`, the `swapdev_block_to_extent` and `swapdev_block_to_offset` functions have become relevant because of the swap allocation tracking. And, `kernel/power/swap.c` has `swsusp_swap_range_allocated` and `swsusp_swap_map_bytes`. So, from a fuzzing perspective, the plan is to focus on these functions: * `snapshot_store_encryption_seed` * `snapshot_get_encryption_key` * `snapshot_set_encryption_key` * `snapshot_set_user_key` * `snapshot_read_encrypted` * `snapshot_write_encrypted` * `snapshot_finalize_decrypted_image` * `hibernate_snapshot_write_begin` * `hibernate_snapshot_write_end` * `swsusp_swap_range_allocated` * `swapdev_block_to_extent` And the key configs to enable are: * `ENCRYPTED_HIBERNATION` * `HIBERNATION_SNAPSHOT_DEV` * `HIBERNATION` This looks like a good starting point for a thorough fuzzing campaign. We are aiming to uncover potential vulnerabilities in the read/write paths, key management, and swap allocation during the hibernation process. Given the number of changes and the core system functionality involved, I'm expecting some interesting findings. |
| 4/2 | 2026/08/04 12:13 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"ENCRYPTED_HIBERNATION",
"HIBERNATION_SNAPSHOT_DEV",
"HIBERNATION"
],
"FocusSymbols": [
"snapshot_store_encryption_seed",
"snapshot_get_encryption_key",
"snapshot_set_encryption_key",
"snapshot_set_user_key",
"snapshot_read_encrypted",
"snapshot_write_encrypted",
"snapshot_finalize_decrypted_image",
"hibernate_snapshot_write_begin",
"hibernate_snapshot_write_end",
"swsusp_swap_range_allocated",
"swapdev_block_to_extent"
],
"Reasoning": "The patch introduces kernel-based encryption for userspace hibernation snapshots. It adds new ioctls to `/dev/snapshot` (`SNAPSHOT_ENABLE_ENCRYPTION`, `SNAPSHOT_SET_USER_KEY`), a sysfs interface `/sys/power/snapshot_seed`, and modifies the read/write paths for the snapshot device to support encryption/decryption. It also hooks into block device writes to track swap allocations. These are reachable core kernel changes that should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"ENCRYPTED_HIBERNATION",
"HIBERNATION_SNAPSHOT_DEV",
"HIBERNATION"
],
"FocusSymbols": [
"snapshot_store_encryption_seed",
"snapshot_get_encryption_key",
"snapshot_set_encryption_key",
"snapshot_set_user_key",
"snapshot_read_encrypted",
"snapshot_write_encrypted",
"snapshot_finalize_decrypted_image",
"hibernate_snapshot_write_begin",
"hibernate_snapshot_write_end",
"swsusp_swap_range_allocated",
"swapdev_block_to_extent"
],
"Reasoning": "The patch introduces kernel-based encryption for userspace hibernation snapshots. It adds new ioctls to `/dev/snapshot` (`SNAPSHOT_ENABLE_ENCRYPTION`, `SNAPSHOT_SET_USER_KEY`), a sysfs interface `/sys/power/snapshot_seed`, and modifies the read/write paths for the snapshot device to support encryption/decryption. It also hooks into block device writes to track swap allocations. These are reachable core kernel changes that should be fuzzed.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|