From nobody Sat Jul 25 21:18:43 2026 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 232663438B5 for ; Mon, 13 Jul 2026 15:39:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783957201; cv=none; b=C3QV5kWMTH9br36YhdQXLLvppcBVJ9booUoPH0C4ZDptIFmAzBDTrs1oMS6ZN7AvMzaPjXGBnFShHoqjyfcoD4TCrbTjQOgHq4ZEbmBD/6TPfNe0RyJKOxrOatCPvajBqhaZgMmTg4Ix6HP/+01NnyR8WCIpBcSftFGojE49In0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783957201; c=relaxed/simple; bh=OliWUamy3MhffOzwkR9gIbn+Al0buUugBLJ0jxPqsTI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WDdiZBDMaqjgSHczsacyHlcEPGx2QPGTXNp3ABRVyLlZ4TJz8wFtJ3vkk/pAg9p+R8cu6uswTSpfXaO6EPAewQXlNL1IBA2qUkRTyia+iksdIkatdYYfU41g9qEmuvhypRF/GiLrr66hugSvzM89G776ZIrabJcurAWB7x4RFT8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=starlabs.systems; spf=pass smtp.mailfrom=starlabs.systems; dkim=pass (2048-bit key) header.d=starlabs-systems.20251104.gappssmtp.com header.i=@starlabs-systems.20251104.gappssmtp.com header.b=ifGnhTcA; arc=none smtp.client-ip=209.85.128.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=starlabs.systems Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=starlabs.systems Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=starlabs-systems.20251104.gappssmtp.com header.i=@starlabs-systems.20251104.gappssmtp.com header.b="ifGnhTcA" Received: by mail-wm1-f42.google.com with SMTP id 5b1f17b1804b1-493f4638f4aso450195e9.3 for ; Mon, 13 Jul 2026 08:39:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=starlabs-systems.20251104.gappssmtp.com; s=20251104; t=1783957194; x=1784561994; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=SDeOJ+BsOGvQcNc60IRgMa4xGcbFC6VXsKcd5NGSnZ8=; b=ifGnhTcAkBL1xq5nAmCMbI9HotjqCc2MmMiFkoWJLLeafMJYRq89sJA/kRfnroOQE3 S6UXyNN5NuUXeAUhPlPG7ifIEck5Yiw46RI4A/6+oeYrWy2FVUVXzBifn5GlklCo+zE6 XfUI2QRkK6nUBAY4plYT2zwz75sH1saDYyA+KljnkO8sKehGXlddUa4VDO8X8Lm5FJkJ 3H/WkZQrnjD8JY8vPJpdfA9ud+bKFuBkkEbDb+TO/2fX6iJfPeyaK9tcm2lg+SdFEDBH 66mMXD51ooVBJVwVQvOgHhCHn20iUWCXCiOX1DMkh4Aciu5auso0kVN5l3Hn3/PEr69K xzSw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783957194; x=1784561994; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=SDeOJ+BsOGvQcNc60IRgMa4xGcbFC6VXsKcd5NGSnZ8=; b=ayPU+xzhbZzgRi2yQXysZkRFkgTNdFhQs6Gxf9lhdCEmagTZxbY7Zrt6pEFP9eGSfR KBXTRgloXQXfoWAXoEf/dLhehgKCl8g6Rr1RW6C0oe4gx/FtROpykEHgz5/xRpncffqh bwUGmbtaJPVQ50R06LIw4q0ing0n9C5cr8JHt01voDvdVAGztrxB0IAAACIlIcP9T03U /hvQ6s0nvaMYvCrdCHWIHVjq1fw2jcIL1+kX8Owd7MGBEI58JVd59EHH0tyCplgp1ShB hrHoU7rz2m25D+b9pq/PSjEl3p/SaQvmxt5VIgpCy8Tyh6OAB1PD/FTvR5vn8DfbTD4h 47Eg== X-Forwarded-Encrypted: i=1; AHgh+RrygoDu4TAef+SLXHCoqdMUnVz7A07jMz8iHUqBcTFt7cd8WIVYM8NgWac5t8dJ57lbyrp549t9XrvbFB8=@vger.kernel.org X-Gm-Message-State: AOJu0YyA+Uqk+nHiVWuF1j/u0hJ8DFR8MA1EW91pkQVZdZI7BMIPZCUC nlOuMNO8IxbjB+YREzqTTCXPhVKmsbh9QvnNkEABok2MxjTsdZEFazegleBIpLChLg== X-Gm-Gg: AfdE7ckCrd0FhbEWYnmAnaqzdUpBTrtd0d0z/87l4WX4Nc6NHzDzXmiEpP+VG78UVUW OANcme7HEDj+278w372Rf0Em6KIgycKgoVaMwJ15qSk+P2dLkbt+R8KGARcWl1palU+y7ZQrpuT y1hTyT8Cg77bPRuL32IWkNX2t2TWkAOX3IQHyCIiGABXpicEXGhlTLypb4xeQIvK5fG2GV9lIzP 6UFYe6o17rhsUzteiaL8jaVJFouyHSm0xyEpQrDVuEl+EwZiHhVnhU3nfJKoe9NO1cM09Z1NiW6 SgsTzqEw1DaaZVfpPuVvzqJqikGeAmewT+uaIfey3+tYQFUU3qHNTRmiYYXifpC6zdpTcPVyPMm z501wwJsv46EDRJ403CejmRiZyf7HGFfjxiAiQreMpcDrshUHy084SmpVHJ2kBaf9SFidEQmqhf XlOapANuCj9ge5mmWptn3mFGRwDAAxd4iKQfE32YJkl74kXl6JfQ5TPGMx X-Received: by 2002:a05:600c:1d15:b0:493:ee2b:c8c7 with SMTP id 5b1f17b1804b1-493f87d5819mr94240905e9.4.1783957194002; Mon, 13 Jul 2026 08:39:54 -0700 (PDT) Received: from horizon.localdomain ([150.228.38.212]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4950a32c65asm3135735e9.14.2026.07.13.08.39.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 13 Jul 2026 08:39:53 -0700 (PDT) From: Sean Rhodes To: "Rafael J. Wysocki" , Len Brown , Pavel Machek , linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Evan Green , Xueqin Luo Subject: [RFC PATCH 1/3] PM: hibernate: add seed-wrapped encrypted snapshots Date: Mon, 13 Jul 2026 16:39:48 +0100 Message-ID: <563bed3eb42f9d294b3603afceabb6856ed760a2.1783956835.git.sean@starlabs.systems> X-Mailer: git-send-email 2.53.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Evan Green Encrypt and authenticate uswsusp hibernation images in the kernel so userspace cannot tamper with the image that will be restored as kernel memory. The image data is protected with gcm(aes) in 16-page chunks. SNAPSHOT_ENABLE_ENCRYPTION enables the encrypted read/write paths and returns an opaque wrapped image-key blob with the starting nonce on suspend. On resume, userspace provides the same blob and nonce after trusted early userspace has written the same 32-byte seed to /sys/power/snapshot_seed. The plaintext image key is generated and unwrapped inside the kernel. The wrapping key is derived from the locked seed, leaving TPM PCR policy and seed unsealing to early userspace rather than adding TPM or trusted-key state to PM code. SNAPSHOT_SET_USER_KEY lets userspace fold extra key material into the data encryption key after the metadata area has been read or written. Tested with 64-bit and 32-bit UAPI size assertions for the new ioctl structures. Also tested with W=3D1 object builds of kernel/power/snapenc.o, kernel/power/hibernate.o, and kernel/power/user.o at this commit. Signed-off-by: Evan Green Co-developed-by: Sean Rhodes Signed-off-by: Sean Rhodes --- Documentation/power/userland-swsusp.rst | 16 + include/uapi/linux/suspend_ioctls.h | 31 +- kernel/power/Kconfig | 15 + kernel/power/Makefile | 1 + kernel/power/hibernate.c | 24 + kernel/power/power.h | 1 + kernel/power/snapenc.c | 878 ++++++++++++++++++++++++ kernel/power/snapshot.c | 5 + kernel/power/user.c | 58 +- kernel/power/user.h | 132 ++++ 10 files changed, 1143 insertions(+), 18 deletions(-) create mode 100644 kernel/power/snapenc.c create mode 100644 kernel/power/user.h diff --git a/Documentation/power/userland-swsusp.rst b/Documentation/power/= userland-swsusp.rst index 1cf62d80a9ca..282e01d2fe61 100644 --- a/Documentation/power/userland-swsusp.rst +++ b/Documentation/power/userland-swsusp.rst @@ -115,6 +115,22 @@ 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) =20 +SNAPSHOT_ENABLE_ENCRYPTION + Enables encryption of the hibernate image within the kernel. Upon suspend + (ie when the snapshot device was opened for reading), returns a blob + representing the random encryption key the kernel created to encrypt the + hibernate image with. Upon resume (ie when the snapshot device was opened + for writing), receives a blob from usermode containing the key material + previously returned during hibernate. + +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. The kernel writes meta_size with the encrypted metadata + size that userspace may transfer before providing the user key during + resume. + The device's read() operation can be used to transfer the snapshot image f= rom the kernel. It has the following limitations: =20 diff --git a/include/uapi/linux/suspend_ioctls.h b/include/uapi/linux/suspe= nd_ioctls.h index bcce04e21c0d..2615dbd03404 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)); =20 +#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 hib= ernate + * image. + */ +struct uswsusp_user_key { + /* Kernel returns the metadata size. */ + __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_lof= f_t) #define SNAPSHOT_ALLOC_SWAP_PAGE _IOR(SNAPSHOT_IOC_MAGIC, 20, __kernel_lof= f_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 =20 #endif /* _LINUX_SUSPEND_IOCTLS_H */ diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 530c897311d4..a9bee71571cf 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. =20 +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 773e2789412b..2fd31b2a250f 100644 --- a/kernel/power/Makefile +++ b/kernel/power/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_SUSPEND) +=3D suspend.o obj-$(CONFIG_PM_TEST_SUSPEND) +=3D suspend_test.o obj-$(CONFIG_HIBERNATION) +=3D hibernate.o snapshot.o swap.o obj-$(CONFIG_HIBERNATION_SNAPSHOT_DEV) +=3D user.o +obj-$(CONFIG_ENCRYPTED_HIBERNATION) +=3D snapenc.o obj-$(CONFIG_PM_AUTOSLEEP) +=3D autosleep.o obj-$(CONFIG_PM_WAKELOCKS) +=3D wakelock.o =20 diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index d2479c69d71a..a95cb447a13a 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -36,6 +36,7 @@ #include =20 #include "power.h" +#include "user.h" =20 =20 static int nocompress; @@ -1376,12 +1377,35 @@ static ssize_t reserved_size_store(struct kobject *= kobj, =20 power_attr(reserved_size); =20 +#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 =3D snapshot_store_encryption_seed(buf, n); + return ret ? ret : n; +} + +static struct kobj_attribute snapshot_seed_attr =3D { + .attr =3D { + .name =3D "snapshot_seed", + .mode =3D 0200, + }, + .store =3D snapshot_seed_store, +}; +#endif + static struct attribute *g[] =3D { &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, }; =20 diff --git a/kernel/power/power.h b/kernel/power/power.h index 75b63843886e..e080230f97fc 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h @@ -160,6 +160,7 @@ struct snapshot_handle { =20 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); diff --git a/kernel/power/snapenc.c b/kernel/power/snapenc.c new file mode 100644 index 000000000000..e6b2fcad9807 --- /dev/null +++ b/kernel/power/snapenc.c @@ -0,0 +1,878 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* This file provides encryption support for system snapshots. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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] =3D { + '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 =3D count; + int ret; + + if (len && buf[len - 1] =3D=3D '\n') + len--; + if (len !=3D SNAPSHOT_SEED_SIZE * 2) + return -EINVAL; + + ret =3D hex2bin(seed, buf, sizeof(seed)); + if (ret) + return ret; + + mutex_lock(&snapshot_seed_mutex); + if (snapshot_seed_valid) { + ret =3D memcmp(snapshot_seed, seed, sizeof(seed)) ? -EPERM : 0; + goto out; + } + + memcpy(snapshot_seed, seed, sizeof(seed)); + snapshot_seed_valid =3D 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 =3D 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_SIZ= E]) +{ + static const char label[] =3D "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 =3D snapshot_derive_wrapping_key(wrap_key); + if (rc) + return rc; + + tfm =3D crypto_alloc_aead("gcm(aes)", 0, 0); + if (IS_ERR(tfm)) { + rc =3D PTR_ERR(tfm); + goto out_key; + } + + rc =3D crypto_aead_setkey(tfm, wrap_key, sizeof(wrap_key)); + if (rc) + goto out_tfm; + + rc =3D crypto_aead_setauthsize(tfm, SNAPSHOT_AUTH_TAG_SIZE); + if (rc) + goto out_tfm; + + req =3D aead_request_alloc(tfm, GFP_KERNEL); + if (!req) { + rc =3D -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 =3D 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 =3D cpu_to_le32(SNAPSHOT_KEY_BLOB_VERSION); + + memcpy(key, image_key, sizeof(key)); + rc =3D 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 *wr= apped, + 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) !=3D SNAPSHOT_KEY_BLOB_VERSION) + return -EINVAL; + + tmp =3D *wrapped; + rc =3D 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 =3D 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 =3D 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 =3D=3D data->meta_size) { + return snapshot_use_user_key(data); + } + + return 0; +} + +/* Encrypt more data from the snapshot into the staging area. */ +static int snapshot_encrypt_refill(struct snapshot_data *data) +{ + struct aead_request *req =3D data->aead_req; + u8 nonce[GCM_AES_IV_SIZE]; + DECLARE_CRYPTO_WAIT(wait); + size_t total =3D 0; + int pg_idx; + int res; + + if (data->crypt_total =3D=3D 0) { + data->meta_size =3D snapshot_get_meta_page_count() << PAGE_SHIFT; + } else { + res =3D 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 =3D 0; pg_idx < CHUNK_SIZE; pg_idx++) { + void *buf =3D data->crypt_pages[pg_idx]; + + /* Stop at the meta page boundary to potentially switch keys. */ + if (total && + ((data->crypt_total + total) =3D=3D data->meta_size)) + break; + + res =3D snapshot_read_next(&data->handle); + if (res < 0) + return res; + if (res =3D=3D 0) + break; + + WARN_ON(res !=3D 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 +=3D PAGE_SIZE; + } + + 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 +=3D 1; + /* Total does not include AAD or the auth tag. */ + aead_request_set_crypt(req, data->sg, data->sg, total, nonce); + res =3D crypto_wait_req(crypto_aead_encrypt(req), &wait); + if (res) + return res; + + data->crypt_size =3D total; + data->crypt_total +=3D 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 =3D 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 =3D data->crypt_offset >> PAGE_SHIFT; + total =3D page_count << PAGE_SHIFT; + if (total =3D=3D 0 || total =3D=3D 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 =3D 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) =3D=3D sizeof(data->auth_tag)) { + memcpy(data->auth_tag, data->crypt_pages[pg_idx], + sizeof(data->auth_tag)); + } else if (data->crypt_offset !=3D + ((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 +=3D 1; + aead_request_set_crypt(req, data->sg, data->sg, total + SNAPSHOT_AUTH_TAG= _SIZE, nonce); + res =3D crypto_wait_req(crypto_aead_decrypt(req), &wait); + if (res) + return res; + + data->crypt_size =3D 0; + data->crypt_offset =3D 0; + + /* Push the decrypted pages further down the stack. */ + total =3D 0; + for (pg_idx =3D 0; pg_idx < page_count; pg_idx++) { + void *buf =3D data->crypt_pages[pg_idx]; + + res =3D snapshot_write_next(&data->handle); + if (res < 0) + return res; + if (res =3D=3D 0) + break; + + if (!data_of(data->handle)) + return -EINVAL; + + WARN_ON(res !=3D PAGE_SIZE); + + /* Copy the decrypted page into the snapshot image. */ + memcpy(data_of(data->handle), buf, PAGE_SIZE); + total +=3D PAGE_SIZE; + } + + if (data->crypt_total =3D=3D 0) + data->meta_size =3D snapshot_get_meta_page_count() << PAGE_SHIFT; + + data->crypt_total +=3D total; + res =3D 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 =3D=3D 0 || + (data->crypt_offset >=3D + (data->crypt_size + SNAPSHOT_AUTH_TAG_SIZE))) { + int rc; + + data->crypt_size =3D 0; + data->crypt_offset =3D 0; + rc =3D snapshot_encrypt_refill(data); + if (rc < 0) + return rc; + } + + /* Return data pages if the offset is in that region. */ + if (data->crypt_offset < data->crypt_size) { + size_t pg_idx =3D data->crypt_offset >> PAGE_SHIFT; + size_t pg_off =3D data->crypt_offset & (PAGE_SIZE - 1); + *buf =3D data->crypt_pages[pg_idx] + pg_off; + return PAGE_SIZE - pg_off; + } + + /* Use offsets just beyond the size to return the tag. */ + tag_off =3D data->crypt_offset - data->crypt_size; + if (tag_off > SNAPSHOT_AUTH_TAG_SIZE) + tag_off =3D SNAPSHOT_AUTH_TAG_SIZE; + + *buf =3D 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 tag_off; + + /* Return data pages if the offset is in that region. */ + if (data->crypt_offset < (PAGE_SIZE * CHUNK_SIZE)) { + size_t pg_idx =3D data->crypt_offset >> PAGE_SHIFT; + size_t pg_off =3D data->crypt_offset & (PAGE_SIZE - 1); + size_t size_avail =3D PAGE_SIZE; + *buf =3D data->crypt_pages[pg_idx] + pg_off; + + /* + * If this is the boundary where the meta pages end, then just + * return enough for the auth tag. + */ + if (data->meta_size && + data->crypt_total < data->meta_size) { + u64 total_done =3D + data->crypt_total + data->crypt_offset; + + if (total_done >=3D data->meta_size && + (total_done < + (data->meta_size + SNAPSHOT_AUTH_TAG_SIZE))) { + size_avail =3D SNAPSHOT_AUTH_TAG_SIZE; + } + } + + return size_avail - pg_off; + } + + /* Use offsets just beyond the size to return the tag. */ + tag_off =3D data->crypt_offset - (PAGE_SIZE * CHUNK_SIZE); + if (tag_off > SNAPSHOT_AUTH_TAG_SIZE) + tag_off =3D SNAPSHOT_AUTH_TAG_SIZE; + + *buf =3D data->auth_tag + tag_off; + return SNAPSHOT_AUTH_TAG_SIZE - tag_off; +} + +ssize_t snapshot_read_encrypted(struct snapshot_data *data, + char __user *buf, size_t count, loff_t *offp) +{ + ssize_t total =3D 0; + + /* Loop getting buffers of varying sizes and copying to userspace. */ + while (count) { + size_t copy_size; + size_t not_done; + void *src; + ssize_t src_size =3D snapshot_read_next_encrypted(data, &src); + + if (src_size <=3D 0) { + if (total =3D=3D 0) + return src_size; + + break; + } + + copy_size =3D min(count, (size_t)src_size); + not_done =3D copy_to_user(buf + total, src, copy_size); + copy_size -=3D not_done; + total +=3D copy_size; + count -=3D copy_size; + data->crypt_offset +=3D copy_size; + if (copy_size =3D=3D 0) { + if (total =3D=3D 0) + return -EFAULT; + + break; + } + } + + *offp +=3D total; + return total; +} + +ssize_t snapshot_write_encrypted(struct snapshot_data *data, + const char __user *buf, size_t count, + loff_t *offp) +{ + ssize_t total =3D 0; + + /* Loop getting buffers of varying sizes and copying from. */ + while (count) { + size_t copy_size; + size_t not_done; + void *dst; + ssize_t dst_size =3D snapshot_write_next_encrypted(data, &dst); + + if (dst_size <=3D 0) { + if (total =3D=3D 0) + return dst_size; + + break; + } + + copy_size =3D min(count, (size_t)dst_size); + not_done =3D copy_from_user(dst, buf + total, copy_size); + copy_size -=3D not_done; + total +=3D copy_size; + count -=3D copy_size; + data->crypt_offset +=3D copy_size; + if (copy_size =3D=3D 0) { + if (total =3D=3D 0) + return -EFAULT; + + break; + } + + /* + * Drain the encrypted buffer if it's full, or if we hit the end + * of the meta pages and need a key change. + */ + if (data->crypt_offset >=3D + (PAGE_SIZE * CHUNK_SIZE) + SNAPSHOT_AUTH_TAG_SIZE || + (data->meta_size && + data->crypt_total < data->meta_size && + data->crypt_total + data->crypt_offset =3D=3D + data->meta_size + SNAPSHOT_AUTH_TAG_SIZE)) { + int rc; + + rc =3D snapshot_decrypt_drain(data); + if (rc < 0) + return rc; + } + } + + *offp +=3D total; + return total; +} + +void snapshot_teardown_encryption(struct snapshot_data *data) +{ + int i; + + if (data->aead_req) { + aead_request_free(data->aead_req); + data->aead_req =3D NULL; + } + + if (data->aead_tfm) { + crypto_free_aead(data->aead_tfm); + data->aead_tfm =3D NULL; + } + + for (i =3D 0; i < CHUNK_SIZE; i++) { + if (data->crypt_pages[i]) { + free_page((unsigned long)data->crypt_pages[i]); + data->crypt_pages[i] =3D NULL; + } + } + + memzero_explicit(data->encryption_key, sizeof(data->encryption_key)); + memzero_explicit(data->user_key, sizeof(data->user_key)); +} + +static int snapshot_setup_encryption_common(struct snapshot_data *data) +{ + int i, rc; + + data->crypt_total =3D 0; + data->crypt_offset =3D 0; + data->crypt_size =3D 0; + data->user_key_valid =3D false; + memset(data->crypt_pages, 0, sizeof(data->crypt_pages)); + /* This only works once per hibernate. */ + if (data->aead_tfm) + return -EINVAL; + + /* Set up the encryption transform */ + data->aead_tfm =3D crypto_alloc_aead("gcm(aes)", 0, 0); + if (IS_ERR(data->aead_tfm)) { + rc =3D PTR_ERR(data->aead_tfm); + data->aead_tfm =3D NULL; + return rc; + } + + rc =3D -ENOMEM; + data->aead_req =3D aead_request_alloc(data->aead_tfm, GFP_KERNEL); + if (!data->aead_req) + goto setup_fail; + + /* Allocate the staging area */ + for (i =3D 0; i < CHUNK_SIZE; i++) { + data->crypt_pages[i] =3D (void *)__get_free_page(GFP_ATOMIC); + 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 =3D 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 =3D {}; + u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring =3D {}; + 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 =3D 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 =3D snapshot_install_image_key(data, image_key); + if (rc) + goto fail; + + rc =3D snapshot_wrap_image_key(image_key, &wrapped); + if (rc) + goto fail; + + /* Hand the wrapped key and clear nonce back to user mode. */ + rc =3D put_user(sizeof(wrapped), &key->blob_len); + if (rc) + goto fail; + + BUILD_BUG_ON(sizeof(wrapped) > + sizeof(((struct uswsusp_key_blob *)0)->blob)); + rc =3D copy_to_user(&key->blob, &wrapped, sizeof(wrapped)); + if (rc) + goto fail; + + rc =3D copy_to_user(&key->nonce, &nonce, sizeof(nonce)); + if (rc) + 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 =3D {}; + u8 image_key[SNAPSHOT_ENCRYPTION_KEY_SIZE] __nonstring =3D {}; + int rc; + + /* It's too late if data's been pushed in. */ + if (data->handle.cur) + return -EPIPE; + + rc =3D snapshot_setup_encryption_common(data); + if (rc) + return rc; + + /* Load the key from user mode. */ + blob =3D memdup_user(key, sizeof(*blob)); + if (IS_ERR(blob)) { + rc =3D PTR_ERR(blob); + goto crypto_setup_fail; + } + + if (blob->blob_len !=3D sizeof(wrapped)) { + rc =3D -EINVAL; + goto out_blob; + } + + memcpy(&wrapped, blob->blob, sizeof(wrapped)); + rc =3D snapshot_unwrap_image_key(&wrapped, image_key); + if (rc) + goto out_blob; + + rc =3D 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 =3D plain_size >> PAGE_SHIFT; + loff_t chunks =3D (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_get_meta_data_size(void) +{ + loff_t pages =3D snapshot_get_meta_page_count(); + + return snapshot_encrypted_byte_count(pages << PAGE_SHIFT); +} + +int snapshot_set_user_key(struct snapshot_data *data, + struct uswsusp_user_key __user *key) +{ + struct uswsusp_user_key user_key; + unsigned int key_len; + u64 size; + int rc; + + /* + * Return the metadata size, the number of bytes that can be fed in before + * the user data key is needed at resume time. + */ + size =3D snapshot_get_meta_data_size(); + rc =3D put_user(size, &key->meta_size); + if (rc) + return rc; + + rc =3D copy_from_user(&user_key, key, sizeof(struct uswsusp_user_key)); + if (rc) + return rc; + + BUILD_BUG_ON(sizeof(data->user_key) < sizeof(user_key.key)); + if (user_key.reserved) + return -EINVAL; + if (user_key.key_len > sizeof(data->user_key)) + return -EINVAL; + if (user_key.key_len < 8) + return -EINVAL; + + key_len =3D user_key.key_len; + + /* Don't allow it if it's too late. */ + if (data->crypt_total > data->meta_size) + return -EBUSY; + + memset(data->user_key, 0, sizeof(data->user_key)); + memcpy(data->user_key, user_key.key, key_len); + data->user_key_valid =3D true; + /* Install the key if the user is just under the wire. */ + rc =3D snapshot_check_user_key_switch(data); + if (rc) + return rc; + + return 0; +} + +loff_t snapshot_get_encrypted_image_size(loff_t raw_size) +{ + loff_t pages =3D raw_size >> PAGE_SHIFT; + loff_t meta_size; + + pages -=3D snapshot_get_meta_page_count(); + meta_size =3D snapshot_get_meta_data_size(); + return snapshot_encrypted_byte_count(pages << PAGE_SHIFT) + meta_size; +} + +int snapshot_finalize_decrypted_image(struct snapshot_data *data) +{ + int rc; + + if (data->crypt_offset !=3D 0) { + rc =3D snapshot_decrypt_drain(data); + if (rc) + return rc; + } + + return 0; +} diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index d933b5b2c05d..02400ac125c5 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; } =20 +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/user.c b/kernel/power/user.c index d0fcfba7ac23..10a61a9f5df0 100644 --- a/kernel/power/user.c +++ b/kernel/power/user.c @@ -25,19 +25,10 @@ #include =20 #include "power.h" +#include "user.h" =20 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; =20 int is_hibernate_resume_dev(dev_t dev) { @@ -57,13 +48,13 @@ static int snapshot_open(struct inode *inode, struct fi= le *filp) =20 if (!hibernate_acquire()) { error =3D -EBUSY; - goto Unlock; + goto unlock; } =20 if ((filp->f_flags & O_ACCMODE) =3D=3D O_RDWR) { hibernate_release(); error =3D -ENOSYS; - goto Unlock; + goto unlock; } nonseekable_open(inode, filp); data =3D &snapshot_state; @@ -100,7 +91,7 @@ static int snapshot_open(struct inode *inode, struct fil= e *filp) data->platform_support =3D false; data->dev =3D 0; =20 - Unlock: + unlock: unlock_system_sleep(sleep_flags); =20 return error; @@ -125,6 +116,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 =3D=3D O_RDONLY ? PM_POST_HIBERNATION : PM_POST_RESTORE); hibernate_release(); @@ -147,12 +139,18 @@ static ssize_t snapshot_read(struct file *filp, char = __user *buf, data =3D filp->private_data; if (!data->ready) { res =3D -ENODATA; - goto Unlock; + goto unlock; + } + + if (snapshot_encryption_enabled(data)) { + res =3D snapshot_read_encrypted(data, buf, count, offp); + goto unlock; } + if (!pg_offp) { /* on page boundary? */ res =3D snapshot_read_next(&data->handle); if (res <=3D 0) - goto Unlock; + goto unlock; } else { res =3D PAGE_SIZE - pg_offp; } @@ -162,7 +160,7 @@ static ssize_t snapshot_read(struct file *filp, char __= user *buf, if (res > 0) *offp +=3D res; =20 - Unlock: + unlock: unlock_system_sleep(sleep_flags); =20 return res; @@ -185,6 +183,11 @@ static ssize_t snapshot_write(struct file *filp, const= char __user *buf, =20 data =3D filp->private_data; =20 + if (snapshot_encryption_enabled(data)) { + res =3D snapshot_write_encrypted(data, buf, count, offp); + goto unlock; + } + if (!pg_offp) { res =3D snapshot_write_next(&data->handle); if (res <=3D 0) @@ -328,6 +331,12 @@ static long snapshot_ioctl(struct file *filp, unsigned= int cmd, break; =20 case SNAPSHOT_ATOMIC_RESTORE: + if (snapshot_encryption_enabled(data)) { + error =3D snapshot_finalize_decrypted_image(data); + if (error) + break; + } + error =3D snapshot_write_finalize(&data->handle); if (error) break; @@ -368,6 +377,8 @@ static long snapshot_ioctl(struct file *filp, unsigned = int cmd, } size =3D snapshot_get_image_size(); size <<=3D PAGE_SHIFT; + if (snapshot_encryption_enabled(data)) + size =3D snapshot_get_encrypted_image_size(size); error =3D put_user(size, (loff_t __user *)arg); break; =20 @@ -425,6 +436,17 @@ static long snapshot_ioctl(struct file *filp, unsigned= int cmd, error =3D snapshot_set_swap_area(data, (void __user *)arg); break; =20 + case SNAPSHOT_ENABLE_ENCRYPTION: + if (data->mode =3D=3D O_RDONLY) + error =3D snapshot_get_encryption_key(data, (void __user *)arg); + else + error =3D snapshot_set_encryption_key(data, (void __user *)arg); + break; + + case SNAPSHOT_SET_USER_KEY: + error =3D snapshot_set_user_key(data, (void __user *)arg); + break; + default: error =3D -ENOTTY; =20 @@ -448,6 +470,8 @@ snapshot_compat_ioctl(struct file *file, unsigned int c= md, 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 000000000000..e13a0ac439f3 --- /dev/null +++ b/kernel/power/user.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include +#include +#include +#include + +#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; + 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 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; +#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(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 c= ount) +{ + return -ENOTTY; +} + +static inline loff_t snapshot_get_encrypted_image_size(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 --=20 2.53.0 From nobody Sat Jul 25 21:18:43 2026 Received: from mail-wm1-f44.google.com (mail-wm1-f44.google.com [209.85.128.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A3CDA38D6B8 for ; Mon, 13 Jul 2026 15:39:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783957199; cv=none; b=glzeHdKA88rxYRonxRIu/W/qh+BtVI+bqTT3HUNrDmNlF6CC0ukuWy+6yBA6eI1VzZ6kZcVPz9Ik++BEvUsEwxXwRceLJExArag7NG+EtMVC4N9Sxgi4rqC/qsInVtI6l1zUWk+dW3meoaCTXf5S88ZEboWyU7bGlac9te4sD+Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783957199; c=relaxed/simple; bh=OJmBnogh4Hr3yJOCRiCPeqPQSUkuyyigEccGcXf3vdQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=E19y1KV9dlARhzguAu2Ix1bM+yXzcbkWZxsxcMmxwrOX9DAzLlULeb1PkVl1D+gTteJMevOMizw3RM1vhCtoFX9oTndBIzepKQV4BQmZFLhR39QHTFuBoDIF5q8+boXoRpNH9kYDa+UK+KZymBzeb+nUNm9Vea0UyRA0WXRj/8o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=starlabs.systems; spf=pass smtp.mailfrom=starlabs.systems; dkim=pass (2048-bit key) header.d=starlabs-systems.20251104.gappssmtp.com header.i=@starlabs-systems.20251104.gappssmtp.com header.b=0QTO8gtK; arc=none smtp.client-ip=209.85.128.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=starlabs.systems Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=starlabs.systems Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=starlabs-systems.20251104.gappssmtp.com header.i=@starlabs-systems.20251104.gappssmtp.com header.b="0QTO8gtK" Received: by mail-wm1-f44.google.com with SMTP id 5b1f17b1804b1-493d92b7db3so505665e9.2 for ; Mon, 13 Jul 2026 08:39:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=starlabs-systems.20251104.gappssmtp.com; s=20251104; t=1783957195; x=1784561995; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=RL9WlQy735iWpsTxb6M4TeNx6F4VhW6ZDui1muGiRg8=; b=0QTO8gtKsqs7KxkqtUWnOQlSy6/V790yRBrxQnxmhlg0N94tU2FMWxzIHW3KUoiaQT aOsGwny2TubBe6NX/4VnRrtJjhtwKhyC1CGF2Ye8PGc0zmF6qDYNKzITjCyGRPoap/6a p3i+SqiovrP0WtUC9udj/yTsyGhuXqoK6zB17D3cItvZdcB+33jOBPzksLG4JQqBX6xD MxGicu/eidL7rhQeF8ajhBsY/GL4ryUua4Bsa5hRxXu2+X1ZsLqMkZfBDdKYeNVDLg2u k5U8G0qu9M+WobFs0ncNckf69jFwgBViniQK/tkbTKzbrJOkPwN2mLydKsbj6SERu71X eRTQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783957195; x=1784561995; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=RL9WlQy735iWpsTxb6M4TeNx6F4VhW6ZDui1muGiRg8=; b=M/IjIZvTH1wQ2/0q6fYwoyrJDUa3w5avpWu7jJmBmmo7bO38p3KLa7bHeWBEEYg/Zp RqQWqMatkOw5AvFoqbzgzCdgVnI+mfecO4IgbNoWxbWodlknwFGc9UbqC7Gzq9pfjXoO sXgsWvS2whvGDBd/ATyT/s6kP03CEhQCDBiB4whg15n/T6Gs62SFaZEZVPHTkLrqBy12 HF70hwHcl1O8T6IDMGqlQoYDyWtlpnecP2HZg1TZkkc7C6D6OcZ70u6osGL4Obnx2Elx GAR+aSDgrgpjcATbrYrtkOsGaoraIww8uflxMwKKcl8YoJlxRv2hIycm2rN4zEHXGIbe AFWw== X-Forwarded-Encrypted: i=1; AHgh+RpSW/P5qoFQy8A5RkNfN2pnr4Jr4egK/olnz0hxfeJF+P4VJvPnig0WB75PhqqUOirIygUow8OGL55As64=@vger.kernel.org X-Gm-Message-State: AOJu0Yz8c1pxJFAwrU9wS5axyH2HkRJzo58ogQIAinIdxPqm6rN5kxGr wAc+vrAagEjis7JxCpbTcadgbWy32lO+fFFIMtURTHJutCC64hNhB/YssSF5OiN5cw== X-Gm-Gg: AfdE7cm4wa6PrT+SzH0bSiWGMbo1ElkLsK7NrU2J+L8N8ILODJoDDa7x6exXreIv1lB tG3XHtZDJlTKfeRCeve/AfBuzbb+pU4+BN5hZAnK1P1YASt2HH5LlIqEB/Q5IBKVWTZhMmtLEAI tf5CjPd0ag0CKkgH+bcvCTAQBiWqL5bGo1b8Yme5cR9VYMM8YMP8i1CwG+AW6z22Pp3WiHYrGmt K8NKcHghyRvaromLviXc9SB3OH2zVcchMkMC+7gUamzg7WsczrLU2QhUmC3RXLTeuO7IAmjYfZ7 sjuMUuQeD+oke3GUYJyUF4Y6rQat2ZwPt0qKVLmusYwSo74+rxcgi7P8Gh6uiHq2D5jCCuJNQyN 0mWVEC8DQ+5NnV+NHHMXcQUBXBhWg8/VoRISLgEUUQ6+eoElyhOv08ROBUC5oWN+vznf6eysrys egYpafXQBSIE2B03BPTyM7j4zK32EKywtjIDsOkNvesNlCNLUThBPyCaQl X-Received: by 2002:a05:600c:154e:b0:493:f0f5:f2de with SMTP id 5b1f17b1804b1-493f87e47dbmr111341305e9.12.1783957195074; Mon, 13 Jul 2026 08:39:55 -0700 (PDT) Received: from horizon.localdomain ([150.228.38.212]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4950a32c65asm3135735e9.14.2026.07.13.08.39.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 13 Jul 2026 08:39:54 -0700 (PDT) From: Sean Rhodes To: "Rafael J. Wysocki" , Len Brown , Pavel Machek , linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Evan Green , Xueqin Luo Subject: [RFC PATCH 2/3] PM: hibernate: permit encrypted snapshot device under lockdown Date: Mon, 13 Jul 2026 16:39:49 +0100 Message-ID: <2283515604cff508c1461a106c55a9ac43590ef2.1783956835.git.sean@starlabs.systems> X-Mailer: git-send-email 2.53.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Lockdown disables hibernation because restoring attacker-controlled image data can modify kernel memory. Permit opening the userspace snapshot device under lockdown when encrypted hibernation support is built, but require encryption to be enabled before image data can be read or written and before restore or power-off ioctls can proceed. Tested with W=3D1 object builds of kernel/power/snapenc.o, kernel/power/hibernate.o, and kernel/power/user.o at this commit. Signed-off-by: Sean Rhodes --- kernel/power/hibernate.c | 8 ++++++++ kernel/power/power.h | 1 + kernel/power/user.c | 36 ++++++++++++++++++++++++++++++++++-- kernel/power/user.h | 1 + 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index a95cb447a13a..0b8626bdf81a 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -114,6 +114,14 @@ bool hibernation_available(void) !secretmem_active() && !cxl_mem_active(); } =20 +bool hibernation_snapshot_dev_available(void) +{ + return nohibernate =3D=3D 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 transitio= ns. diff --git a/kernel/power/power.h b/kernel/power/power.h index e080230f97fc..68ed191b905c 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h @@ -168,6 +168,7 @@ extern int snapshot_image_loaded(struct snapshot_handle= *handle); =20 extern bool hibernate_acquire(void); extern void hibernate_release(void); +bool hibernation_snapshot_dev_available(void); =20 extern sector_t alloc_swapdev_block(int swap); extern void free_all_swap_pages(int swap); diff --git a/kernel/power/user.c b/kernel/power/user.c index 10a61a9f5df0..d30aed4bc35f 100644 --- a/kernel/power/user.c +++ b/kernel/power/user.c @@ -21,6 +21,7 @@ #include #include #include +#include =20 #include =20 @@ -32,7 +33,12 @@ struct snapshot_data snapshot_state; =20 int is_hibernate_resume_dev(dev_t dev) { - return hibernation_available() && snapshot_state.dev =3D=3D dev; + return hibernation_snapshot_dev_available() && snapshot_state.dev =3D=3D = dev; +} + +static bool snapshot_encryption_required(void) +{ + return security_locked_down(LOCKDOWN_HIBERNATION); } =20 static int snapshot_open(struct inode *inode, struct file *filp) @@ -41,7 +47,7 @@ static int snapshot_open(struct inode *inode, struct file= *filp) unsigned int sleep_flags; int error; =20 - if (!hibernation_available()) + if (!hibernation_snapshot_dev_available()) return -EPERM; =20 sleep_flags =3D lock_system_sleep(); @@ -90,6 +96,7 @@ static int snapshot_open(struct inode *inode, struct file= *filp) data->ready =3D false; data->platform_support =3D false; data->dev =3D 0; + data->encryption_required =3D snapshot_encryption_required(); =20 unlock: unlock_system_sleep(sleep_flags); @@ -141,6 +148,10 @@ static ssize_t snapshot_read(struct file *filp, char _= _user *buf, res =3D -ENODATA; goto unlock; } + if (data->encryption_required && !snapshot_encryption_enabled(data)) { + res =3D -EPERM; + goto unlock; + } =20 if (snapshot_encryption_enabled(data)) { res =3D snapshot_read_encrypted(data, buf, count, offp); @@ -183,6 +194,11 @@ static ssize_t snapshot_write(struct file *filp, const= char __user *buf, =20 data =3D filp->private_data; =20 + if (data->encryption_required && !snapshot_encryption_enabled(data)) { + res =3D -EPERM; + goto unlock; + } + if (snapshot_encryption_enabled(data)) { res =3D snapshot_write_encrypted(data, buf, count, offp); goto unlock; @@ -321,6 +337,10 @@ static long snapshot_ioctl(struct file *filp, unsigned= int cmd, error =3D -EPERM; break; } + if (data->encryption_required && !snapshot_encryption_enabled(data)) { + error =3D -EPERM; + break; + } pm_restore_gfp_mask(); error =3D hibernation_snapshot(data->platform_support); if (!error) { @@ -331,6 +351,10 @@ static long snapshot_ioctl(struct file *filp, unsigned= int cmd, break; =20 case SNAPSHOT_ATOMIC_RESTORE: + if (data->encryption_required && !snapshot_encryption_enabled(data)) { + error =3D -EPERM; + break; + } if (snapshot_encryption_enabled(data)) { error =3D snapshot_finalize_decrypted_image(data); if (error) @@ -415,6 +439,10 @@ static long snapshot_ioctl(struct file *filp, unsigned= int cmd, error =3D -EPERM; break; } + if (data->encryption_required && !snapshot_encryption_enabled(data)) { + error =3D -EPERM; + break; + } /* * Tasks are frozen and the notifiers have been called with * PM_HIBERNATION_PREPARE @@ -428,6 +456,10 @@ static long snapshot_ioctl(struct file *filp, unsigned= int cmd, break; =20 case SNAPSHOT_POWER_OFF: + if (data->encryption_required && !snapshot_encryption_enabled(data)) { + error =3D -EPERM; + break; + } if (data->platform_support) error =3D hibernation_platform_enter(); break; diff --git a/kernel/power/user.h b/kernel/power/user.h index e13a0ac439f3..3c3498bdb752 100644 --- a/kernel/power/user.h +++ b/kernel/power/user.h @@ -20,6 +20,7 @@ struct snapshot_data { bool ready; bool platform_support; bool free_bitmaps; + bool encryption_required; dev_t dev; =20 #if defined(CONFIG_ENCRYPTED_HIBERNATION) --=20 2.53.0 From nobody Sat Jul 25 21:18:43 2026 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F04513DE44D for ; Mon, 13 Jul 2026 15:39:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783957200; cv=none; b=fMbHhWp6yveZM+eVSD1SAXqem5ZFUw9G+/Cj7roflrhLroHWIATDRN4xJhbCT6LrJuVEiXs09GtNl7WI+EipKB4XfthTHdQBr052q9uVdO4qmk3KizTiPtyFB/wYTQ95H2Ne7mWrxAOAXRynvxf4kwL3QBQGsEIt4xF+pgJiPj0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783957200; c=relaxed/simple; bh=yNZ82j0E6cOU7M4TEVWTmABgsqSuolUg23P+N9dX5Jk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ELtfg4pPz7YblqTW4nBJayjL+PaLOdymWmVpksrmwBy6F0pfKSPNHOjoG5rKp8H500Sff+uGIpwX1kca95bVZZcoC0r6myn7ZCpQytAw9k15ZZCpaRv3XWTenirpPXUI1VtPv/JqRdgilzmUiyBuvH9jPDOeWJWyNIe1qclhAtc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=starlabs.systems; spf=pass smtp.mailfrom=starlabs.systems; dkim=pass (2048-bit key) header.d=starlabs-systems.20251104.gappssmtp.com header.i=@starlabs-systems.20251104.gappssmtp.com header.b=LjuOaaP1; arc=none smtp.client-ip=209.85.128.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=starlabs.systems Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=starlabs.systems Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=starlabs-systems.20251104.gappssmtp.com header.i=@starlabs-systems.20251104.gappssmtp.com header.b="LjuOaaP1" Received: by mail-wm1-f42.google.com with SMTP id 5b1f17b1804b1-493b27c7451so240245e9.0 for ; Mon, 13 Jul 2026 08:39:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=starlabs-systems.20251104.gappssmtp.com; s=20251104; t=1783957196; x=1784561996; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=SXlrSFErYtjENzy3YZB0q3w2dAvBYZj3GABSDzvON6k=; b=LjuOaaP1HmFZzhIQGBQnAuqp6Co1juPTeqCmVe3zKg+0mwa1I13P/3UHTgQ25lXUxk lTXUMYoE2un56lLtpmKtrwKwH5cehok9WvMs6xAjHNKWV8ogCz58kyn9cTnlL3rmgkJ+ wDytMOG360fNfRR9QBZeH1xek90P2LeBDz0JipZtyuMDXWEyA0sd/Tr3GFWdp2IufvTh eVIIPLPKdcVh8HD3LpC+Z9neeifq6seE0+Ws3UuiWUpTkAqQakGdCpCR9tekj/ypei6E OR82XzVYVM8QoO40ecYhavM0ofx2W4x/JG2C3Pr7I52JREDLabTxGMYPgQw30sujYZH9 da5w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783957196; x=1784561996; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=SXlrSFErYtjENzy3YZB0q3w2dAvBYZj3GABSDzvON6k=; b=DwuwFMi5H7TxAQ8KhOs7vaoX73gINaRJkSraWIcy0YzG+rnir+oJnTfj6LlY8w0bvX IqmeYoaeBnxjuxlp+R0EEbIWv99g4+23iXJOq9NTVxs7ny8GFsAWNiJkPlcY4Z24aD69 X/7uz07/wp8lQCZo17lLO/y7aF3luThuQwq0STqdJsPXymNfFg1VAtTnqUjQyuYi/yv/ qUzmfbRiixs9dUn7eYyVRKds3Y1RCbvey0W1UFo61E9gp7EYLywSOoICDX5XiTh2pf6m GmLrQY4W+CT9eBQxSK7Hnp1RPQXgpIRvq+9E9wkUb73nicW/SeEz9rErU0+cjoBW4MUY lVDQ== X-Forwarded-Encrypted: i=1; AHgh+RrVULj1YonMVHAmDh+rgTxyAU3DMsJ1nXHs4MUzXpa8umvLZyKTBOEG+jv9kVxA2Q1dU20vA82X2WXtqv0=@vger.kernel.org X-Gm-Message-State: AOJu0Yz1xGM9XEc0Smeqc7wv2+RZAVVzwAmuAt769IQcwqv4YDds9X0g /mLllJDZBbFeojIqyokkzr5+mHfgFW3lurTgT/LOroIEz2ACSrps33sdcOti7aTKmw== X-Gm-Gg: AfdE7cl3gmZKxlTtY3SfWCkokSa6sRk2xOhAgVkUTR7N4hSsZjwkZvNcKo2i3SsWWYR kjvrGFBpNadQZoyiN3YzAaFsZf9pr2a4ehf6XNvf5odV8s0AFt7sKLJQumpOH5Y6UAaRRCP73im Q6h4Pqnd5Sg5NO6eCo2TcEXTx9sJYz/XodNwVlZKSFf7wjyCwJ7+ipQgIig7YIZdhx3xWuxLDIj 50kTqcnQ6uK208FAIvTIS5ujO3DkHRCLG9wMm++rpZrZk2qFCuIwFvUW3OIVafPpVqaeskZ4ML8 cr4PtKBiKEj9yYJWm8RNKDEOkQ4mK1eTi72RShkrYraa+bkeOcw7kQhnQK0ckwmlyDwH81q8pmT YlqH8NrEgdqE0QaURLkSCL4+LpwwcADQDmwubCIqxXsKbryQd+/MDkclBKlfHOVdm8h/Lm0nU+D LfGz8P6ZrWek316C+sQUBfv3ZCJZLHdiUcYh1WeKAtM0u0lKnD8W+gJKyl1enHDIDF1pk= X-Received: by 2002:a05:600c:6749:b0:493:edde:54c8 with SMTP id 5b1f17b1804b1-493f8885b92mr105859105e9.8.1783957196262; Mon, 13 Jul 2026 08:39:56 -0700 (PDT) Received: from horizon.localdomain ([150.228.38.212]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4950a32c65asm3135735e9.14.2026.07.13.08.39.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 13 Jul 2026 08:39:55 -0700 (PDT) From: Sean Rhodes To: "Rafael J. Wysocki" , Len Brown , Pavel Machek , linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Evan Green , Xueqin Luo Subject: [RFC PATCH 3/3] PM: hibernate: document encrypted snapshot seed ABI Date: Mon, 13 Jul 2026 16:39:50 +0100 Message-ID: X-Mailer: git-send-email 2.53.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Document the write-only snapshot_seed sysfs file and update the userspace snapshot interface text to describe the opaque wrapped-key flow used by encrypted hibernation. Tested with a final-tree W=3D1 object build of kernel/power/snapenc.o, kernel/power/hibernate.o, and kernel/power/user.o. Signed-off-by: Sean Rhodes --- Documentation/ABI/testing/sysfs-power | 14 ++++++++++++++ Documentation/power/userland-swsusp.rst | 15 +++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/test= ing/sysfs-power index d38da077905a..57f6063ef88c 100644 --- a/Documentation/ABI/testing/sysfs-power +++ b/Documentation/ABI/testing/sysfs-power @@ -470,3 +470,17 @@ Description: =20 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=3Dy. + + 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 282e01d2fe61..777518ac591d 100644 --- a/Documentation/power/userland-swsusp.rst +++ b/Documentation/power/userland-swsusp.rst @@ -116,12 +116,15 @@ SNAPSHOT_S2RAM its state on the basis of the saved suspend image otherwise) =20 SNAPSHOT_ENABLE_ENCRYPTION - Enables encryption of the hibernate image within the kernel. Upon suspend - (ie when the snapshot device was opened for reading), returns a blob - representing the random encryption key the kernel created to encrypt the - hibernate image with. Upon resume (ie when the snapshot device was opened - for writing), receives a blob from usermode containing the key material - previously returned during hibernate. + 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. =20 SNAPSHOT_SET_USER_KEY Mixes additional user key material into the data portion of an encrypted --=20 2.53.0