Received: by 2002:a05:6a10:8c0a:0:0:0:0 with SMTP id go10csp52076pxb; Fri, 19 Feb 2021 17:40:21 -0800 (PST) X-Google-Smtp-Source: ABdhPJyceVrxp8G37tgUVpCjz7MpcQRy9TgHLUNGfeYxus+y762YRUzg44i0jFNd5Tzce5bEFJsA X-Received: by 2002:a17:906:1a56:: with SMTP id j22mr11392430ejf.80.1613785221250; Fri, 19 Feb 2021 17:40:21 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1613785221; cv=none; d=google.com; s=arc-20160816; b=dMjTXbMYDbhvg/ezad+6jtUuVpYMWVb+/ajnNxHr+kCe5p+E3eJCy+NLdyUeIi3NmL z/2WhPLPzCHom5IdeM7f2VHTOAhemLcl8u90dZD++nci8OPjrDIdRqEnJf3RwKnvuEqN AgAuPxjmsqifGYiW7kOgRWcU5vfcjRAVmStK3DcZRw87UVkmxkfp7vpDzNmfy6HHvddK G95nfvVfoTRB9QQV/6he235nGtQfGFFkeRp+Oy5yrmroasXvnsgEflj82qjdYo/QC2p2 JemUC443Y2VQe/Qj3LH87+X4mkTdYhQV2kWWetZ2i4Xzbup0UfPYTSsVifMTbdtwhqZr zwOg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:cc:to:from:subject:references:mime-version :message-id:in-reply-to:date:sender:dkim-signature; bh=+fj/Z6ua/xhvW/t0EkMNvoSpSDcWQcmaOe23w27DhCU=; b=rrll8EJdh97QrRJ1o7wQrV/Z8KdFDovkl8TNlFaJ1thcy7RllpInp6Kw+JZSoaj5Sn nB1c5JDngxtMzaOz8BgVCvY1q6bnQWIcYbyYn/JSKUv8rVZpXQWPcnkHu7pGWWAZ+l9l jy5fTRJZiLRfkU0F+Zc84alocpG16ZG71Qui7B6j1GrefZh+wneLne5MBlBh0wWD+6YG 9VOBZpzXEq9CxuyIukejW64z9OffQBjQQzAZ001QWKEthS9sWAjfJ3JECIVWi515H6XY +rPcfGn6rkHj8ocNWpSmjQ08d3pWIVRdPTydRm2OacPIDGkEunS0mDI6wXc7cos3R3O0 iNPw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=tNj9cgZg; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id r16si7106985ejc.439.2021.02.19.17.39.55; Fri, 19 Feb 2021 17:40:21 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=tNj9cgZg; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230071AbhBTBfx (ORCPT + 99 others); Fri, 19 Feb 2021 20:35:53 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44202 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229762AbhBTBer (ORCPT ); Fri, 19 Feb 2021 20:34:47 -0500 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id BB83AC061A28 for ; Fri, 19 Feb 2021 17:33:18 -0800 (PST) Received: by mail-yb1-xb49.google.com with SMTP id y7so8743062ybh.20 for ; Fri, 19 Feb 2021 17:33:18 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=sender:date:in-reply-to:message-id:mime-version:references:subject :from:to:cc; bh=+fj/Z6ua/xhvW/t0EkMNvoSpSDcWQcmaOe23w27DhCU=; b=tNj9cgZgEKD8n70OZsmqKRRl3GWLRgFQFMJXrCLDOI3GLrM+YESsK63Ocq/x3+Fdoq nCD/3sccAVAjp1dB6HczG/FsyOtZCtDm2eqdOaSQjgNScvpAvUzRn9R3yEuZbaC2Fxze psgUxP1b58V6t9kcxnU1Pd1q3TO0BNdUlwtCsPy67epV5Akx7oJPOPpQg+o9M2/B7cqM VI4zp7Pb7bZv/NY4zD8ZF7G1OtlIp6zLwlOoH2Tk/ZD8yesyDUA0zgZMGeUOcMADwW5v fa9PB5YDdXirQoJSNDMpmpnbyvfw3kmGjnX8TGmLsPSbUs5rHvTSSFp84Kt0/kIvaTEq gNig== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=+fj/Z6ua/xhvW/t0EkMNvoSpSDcWQcmaOe23w27DhCU=; b=WPG00DXX8h08ooQ42aUZBaFChA+xCYWK3/RViCiZAnUMtxsAdbyTNGBHzfEV6t3nRf rg9JeN8UoMj0JFZTMYtza7M+SkeV82CG9Kh2w8Bb/X7QwfMkj6EGPCXF921kjX7KU3YR pKzBQ+y2PwlnhCzoQMFzw/wOtJC5rGqeMQOfVFhtXcdQXhGq6Un2L/9y81Ou+G5M4fN2 5M+xvO7YNPEDFk2+k7va5F0Ek7X+R1WYDUDQja4XVN8TSIQXcGRlkbkQlg5VthwEBz7W DzJcBusWuZqG1LcyurmE4DcoqGTxDcyNUqenSmwfkNd9anW5lyhzGoDYsdcTYctx6wYi 0zNQ== X-Gm-Message-State: AOAM530TQBefr9eDpzGNvtUjaWfUYVJ/rJ1+aqekJBxVrLyGUNiw55GH NV+fd7+LLZfY4owxOaR1R+MfkkTxtcReQ9Aq20T7thPvyBepZxwD0lJJiuLIrOO8pLMYazWVS/A dncIIfbwTFJips/j+9FyWQQW3vzIg9eft5BBPhe7TBzY3oae7T6J04PTnRlob3BgyE2ShXHfe22 oMhBmWQmRtKKjGfsw= Sender: "matthewgarrett via sendgmr" X-Received: from matthewgarrett-tmp.c.googlers.com ([fda3:e722:ac3:10:7f:e700:c0a8:1081]) (user=matthewgarrett job=sendgmr) by 2002:a25:741:: with SMTP id 62mr19123176ybh.469.1613784797905; Fri, 19 Feb 2021 17:33:17 -0800 (PST) Date: Sat, 20 Feb 2021 01:32:53 +0000 In-Reply-To: <20210220013255.1083202-1-matthewgarrett@google.com> Message-Id: <20210220013255.1083202-8-matthewgarrett@google.com> Mime-Version: 1.0 References: <20210220013255.1083202-1-matthewgarrett@google.com> X-Mailer: git-send-email 2.30.0.617.g56c4b15f3c-goog Subject: [PATCH 7/9] pm: hibernate: Optionally use TPM-backed keys to protect image integrity From: Matthew Garrett To: linux-kernel@vger.kernel.org Cc: linux-integrity@vger.kernel.org, linux-pm@vger.kernel.org, keyrings@vger.kernel.org, zohar@linux.ibm.com, jejb@linux.ibm.com, jarkko@kernel.org, corbet@lwn.net, rjw@rjwysocki.net, Matthew Garrett , Matthew Garrett Content-Type: text/plain; charset="UTF-8" Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org A plain hash protects the hibernation image against accidental modification, but in the face of an active attack the hash can simply be updated to match the new image. Generate a random AES key and seal this with the TPM, and use it to encrypt the hash. On resume, the key can be unsealed and used to decrypt the hash. By setting PCR 23 to a specific value we can verify that the key used was generated by the kernel during hibernation and prevent an attacker providing their own key. Signed-off-by: Matthew Garrett --- kernel/power/Kconfig | 15 ++ kernel/power/Makefile | 1 + kernel/power/hibernate.c | 11 +- kernel/power/swap.c | 99 +++---------- kernel/power/swap.h | 38 +++++ kernel/power/tpm.c | 294 +++++++++++++++++++++++++++++++++++++++ kernel/power/tpm.h | 37 +++++ 7 files changed, 417 insertions(+), 78 deletions(-) create mode 100644 kernel/power/swap.h create mode 100644 kernel/power/tpm.c create mode 100644 kernel/power/tpm.h diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index a7320f07689d..0279cc10f319 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -92,6 +92,21 @@ config HIBERNATION_SNAPSHOT_DEV If in doubt, say Y. +config SECURE_HIBERNATION + bool "Implement secure hibernation support" + depends on HIBERNATION && TCG_TPM + select KEYS + select TRUSTED_KEYS + select CRYPTO + select CRYPTO_SHA256 + select CRYPTO_AES + select TCG_TPM_RESTRICT_PCR + help + Use a TPM-backed key to securely determine whether a hibernation + image was written out by the kernel and has not been tampered with. + This requires a TCG-compliant TPM2 device, which is present on most + modern hardware. + config PM_STD_PARTITION string "Default resume partition" depends on HIBERNATION diff --git a/kernel/power/Makefile b/kernel/power/Makefile index 5899260a8bef..2edfef897607 100644 --- a/kernel/power/Makefile +++ b/kernel/power/Makefile @@ -12,6 +12,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_SECURE_HIBERNATION) += tpm.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 da0b41914177..608bfbee38f5 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -34,6 +34,7 @@ #include #include "power.h" +#include "tpm.h" static int nocompress; @@ -81,7 +82,11 @@ void hibernate_release(void) bool hibernation_available(void) { - return nohibernate == 0 && !security_locked_down(LOCKDOWN_HIBERNATION); + if (security_locked_down(LOCKDOWN_HIBERNATION) && + !secure_hibernation_available()) + return false; + + return nohibernate == 0; } /** @@ -752,7 +757,9 @@ int hibernate(void) flags |= SF_NOCOMPRESS_MODE; else flags |= SF_CRC32_MODE; - +#ifdef CONFIG_SECURE_HIBERNATION + flags |= SF_VERIFY_IMAGE; +#endif pm_pr_dbg("Writing hibernation image.\n"); error = swsusp_write(flags); swsusp_free(); diff --git a/kernel/power/swap.c b/kernel/power/swap.c index a13241a20567..eaa585731314 100644 --- a/kernel/power/swap.c +++ b/kernel/power/swap.c @@ -32,9 +32,10 @@ #include #include #include -#include #include "power.h" +#include "swap.h" +#include "tpm.h" #define HIBERNATE_SIG "S1SUSPEND" @@ -89,34 +90,6 @@ struct swap_map_page_list { struct swap_map_page_list *next; }; -/** - * The swap_map_handle structure is used for handling swap in - * a file-alike way - */ - -struct swap_map_handle { - struct swap_map_page *cur; - struct swap_map_page_list *maps; - struct shash_desc *desc; - sector_t cur_swap; - sector_t first_sector; - unsigned int k; - unsigned long reqd_free_pages; - u32 crc32; - u8 digest[SHA256_DIGEST_SIZE]; -}; - -struct swsusp_header { - char reserved[PAGE_SIZE - 20 - sizeof(sector_t) - sizeof(int) - - sizeof(u32) - SHA256_DIGEST_SIZE]; - u32 crc32; - u8 digest[SHA256_DIGEST_SIZE]; - sector_t image; - unsigned int flags; /* Flags to pass to the "boot" kernel */ - char orig_sig[10]; - char sig[10]; -} __packed; - static struct swsusp_header *swsusp_header; /** @@ -337,6 +310,9 @@ static int mark_swapfiles(struct swap_map_handle *handle, unsigned int flags) swsusp_header->crc32 = handle->crc32; memcpy(swsusp_header->digest, handle->digest, SHA256_DIGEST_SIZE); + error = swsusp_encrypt_digest(swsusp_header); + if (error) + return error; error = hib_submit_io(REQ_OP_WRITE, REQ_SYNC, swsusp_resume_block, swsusp_header, NULL); } else { @@ -427,7 +403,6 @@ static void release_swap_writer(struct swap_map_handle *handle) static int get_swap_writer(struct swap_map_handle *handle) { int ret; - struct crypto_shash *tfm; ret = swsusp_swap_check(); if (ret) { @@ -449,27 +424,11 @@ static int get_swap_writer(struct swap_map_handle *handle) handle->reqd_free_pages = reqd_free_pages(); handle->first_sector = handle->cur_swap; - tfm = crypto_alloc_shash("sha256", 0, 0); - if (IS_ERR(tfm)) { - ret = -EINVAL; - goto err_rel; - } - handle->desc = kmalloc(sizeof(struct shash_desc) + - crypto_shash_descsize(tfm), GFP_KERNEL); - if (!handle->desc) { - ret = -ENOMEM; + ret = swsusp_digest_setup(handle); + if (ret) goto err_rel; - } - - handle->desc->tfm = tfm; - - ret = crypto_shash_init(handle->desc); - if (ret != 0) - goto err_free; return 0; -err_free: - kfree(handle->desc); err_rel: release_swap_writer(handle); err_close: @@ -486,7 +445,7 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, if (!handle->cur) return -EINVAL; offset = alloc_swapdev_block(root_swap); - crypto_shash_update(handle->desc, buf, PAGE_SIZE); + swsusp_digest_update(handle, buf, PAGE_SIZE); error = write_page(buf, offset, hb); if (error) return error; @@ -529,7 +488,7 @@ static int flush_swap_writer(struct swap_map_handle *handle) static int swap_writer_finish(struct swap_map_handle *handle, unsigned int flags, int error) { - crypto_shash_final(handle->desc, handle->digest); + swsusp_digest_final(handle); if (!error) { pr_info("S"); error = mark_swapfiles(handle, flags); @@ -1008,7 +967,6 @@ static int get_swap_reader(struct swap_map_handle *handle, int error; struct swap_map_page_list *tmp, *last; sector_t offset; - struct crypto_shash *tfm; *flags_p = swsusp_header->flags; @@ -1047,27 +1005,12 @@ static int get_swap_reader(struct swap_map_handle *handle, handle->k = 0; handle->cur = handle->maps->map; - tfm = crypto_alloc_shash("sha256", 0, 0); - if (IS_ERR(tfm)) { - error = -EINVAL; - goto err_rel; - } - handle->desc = kmalloc(sizeof(struct shash_desc) + - crypto_shash_descsize(tfm), GFP_KERNEL); - if (!handle->desc) { - error = -ENOMEM; - goto err_rel; - } - - handle->desc->tfm = tfm; + error = swsusp_digest_setup(handle); + if (error) + goto err; - error = crypto_shash_init(handle->desc); - if (error != 0) - goto err_free; return 0; -err_free: - kfree(handle->desc); -err_rel: +err: release_swap_reader(handle); return error; } @@ -1087,7 +1030,7 @@ static int swap_read_page(struct swap_map_handle *handle, void *buf, error = hib_submit_io(REQ_OP_READ, 0, offset, buf, hb); if (error) return error; - crypto_shash_update(handle->desc, buf, PAGE_SIZE); + swsusp_digest_update(handle, buf, PAGE_SIZE); if (++handle->k >= MAP_PAGE_ENTRIES) { handle->k = 0; free_page((unsigned long)handle->maps->map); @@ -1107,11 +1050,13 @@ static int swap_reader_finish(struct swap_map_handle *handle, { int ret = 0; - crypto_shash_final(handle->desc, handle->digest); - if (memcmp(handle->digest, swsusp_header->digest, - SHA256_DIGEST_SIZE) != 0) { - pr_err("Image digest doesn't match header digest\n"); - ret = -ENODATA; + swsusp_digest_final(handle); + if (swsusp_header->flags & SF_VERIFY_IMAGE) { + if (memcmp(handle->digest, swsusp_header->digest, + SHA256_DIGEST_SIZE) != 0) { + pr_err("Image digest doesn't match header digest\n"); + ret = -ENODATA; + } } release_swap_reader(handle); @@ -1630,6 +1575,8 @@ int swsusp_check(void) error = -EINVAL; } + if (!error) + error = swsusp_decrypt_digest(swsusp_header); put: if (error) blkdev_put(hib_resume_bdev, FMODE_READ); diff --git a/kernel/power/swap.h b/kernel/power/swap.h new file mode 100644 index 000000000000..342189344f5f --- /dev/null +++ b/kernel/power/swap.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include +#include + +#ifndef _POWER_SWAP_H +#define _POWER_SWAP_H 1 +/** + * The swap_map_handle structure is used for handling swap in + * a file-alike way + */ + +struct swap_map_handle { + struct swap_map_page *cur; + struct swap_map_page_list *maps; + struct shash_desc *desc; + sector_t cur_swap; + sector_t first_sector; + unsigned int k; + unsigned long reqd_free_pages; + u32 crc32; + u8 digest[SHA256_DIGEST_SIZE]; +}; + +struct swsusp_header { + char reserved[PAGE_SIZE - 20 - sizeof(sector_t) - sizeof(int) - + sizeof(u32) - SHA256_DIGEST_SIZE - MAX_BLOB_SIZE - + sizeof(u32)]; + u32 blob_len; + u8 blob[MAX_BLOB_SIZE]; + u8 digest[SHA256_DIGEST_SIZE]; + u32 crc32; + sector_t image; + unsigned int flags; /* Flags to pass to the "boot" kernel */ + char orig_sig[10]; + char sig[10]; +} __packed; + +#endif /* _POWER_SWAP_H */ diff --git a/kernel/power/tpm.c b/kernel/power/tpm.c new file mode 100644 index 000000000000..953dcbdc56d8 --- /dev/null +++ b/kernel/power/tpm.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include + +#include "swap.h" +#include "tpm.h" + +/* sha256("To sleep, perchance to dream") */ +static struct tpm_digest digest = { .alg_id = TPM_ALG_SHA256, + .digest = {0x92, 0x78, 0x3d, 0x79, 0x2d, 0x00, 0x31, 0xb0, 0x55, 0xf9, + 0x1e, 0x0d, 0xce, 0x83, 0xde, 0x1d, 0xc4, 0xc5, 0x8e, 0x8c, + 0xf1, 0x22, 0x38, 0x6c, 0x33, 0xb1, 0x14, 0xb7, 0xec, 0x05, + 0x5f, 0x49}}; + +struct skcipher_def { + struct scatterlist sg; + struct crypto_skcipher *tfm; + struct skcipher_request *req; + struct crypto_wait wait; +}; + +static int swsusp_enc_dec(struct trusted_key_payload *payload, char *buf, + int enc) +{ + struct skcipher_def sk; + struct crypto_skcipher *skcipher = NULL; + struct skcipher_request *req = NULL; + char *ivdata = NULL; + int ret; + + skcipher = crypto_alloc_skcipher("cbc-aes-aesni", 0, 0); + if (IS_ERR(skcipher)) + return PTR_ERR(skcipher); + + req = skcipher_request_alloc(skcipher, GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto out; + } + + skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, + crypto_req_done, + &sk.wait); + + /* AES 256 */ + if (crypto_skcipher_setkey(skcipher, payload->key, 32)) { + ret = -EAGAIN; + goto out; + } + + /* Key will never be re-used, just fix the IV to 0 */ + ivdata = kzalloc(16, GFP_KERNEL); + if (!ivdata) { + ret = -ENOMEM; + goto out; + } + + sk.tfm = skcipher; + sk.req = req; + + sg_init_one(&sk.sg, buf, 32); + skcipher_request_set_crypt(req, &sk.sg, &sk.sg, 16, ivdata); + crypto_init_wait(&sk.wait); + + /* perform the operation */ + if (enc) + ret = crypto_wait_req(crypto_skcipher_encrypt(sk.req), + &sk.wait); + else + ret = crypto_wait_req(crypto_skcipher_decrypt(sk.req), + &sk.wait); + + if (ret) + pr_info("skcipher encrypt returned with result %d\n", ret); + + goto out; + +out: + if (skcipher) + crypto_free_skcipher(skcipher); + if (req) + skcipher_request_free(req); + kfree(ivdata); + return ret; +} + +int swsusp_encrypt_digest(struct swsusp_header *header) +{ + const struct cred *cred = current_cred(); + struct trusted_key_payload *payload; + struct tpm_digest *digests = NULL; + struct tpm_chip *chip; + struct key *key; + int ret, i; + + char *keyinfo = "new\t32\tkeyhandle=0x81000001"; + + chip = tpm_default_chip(); + + if (!chip) + return -ENODEV; + + if (!(tpm_is_tpm2(chip))) + return -ENODEV; + + ret = tpm_pcr_reset(chip, 23); + if (ret != 0) + return ret; + + digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest), + GFP_KERNEL); + if (!digests) { + ret = -ENOMEM; + goto reset; + } + + for (i = 0; i <= chip->nr_allocated_banks; i++) { + digests[i].alg_id = chip->allocated_banks[i].alg_id; + if (digests[i].alg_id == digest.alg_id) + memcpy(&digests[i], &digest, sizeof(digest)); + } + + ret = tpm_pcr_extend(chip, 23, digests); + if (ret != 0) + goto reset; + + key = key_alloc(&key_type_trusted, "swsusp", GLOBAL_ROOT_UID, + GLOBAL_ROOT_GID, cred, 0, KEY_ALLOC_NOT_IN_QUOTA, + NULL); + + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto reset; + } + + ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL, + NULL); + if (ret < 0) + goto error; + + payload = key->payload.data[0]; + + ret = swsusp_enc_dec(payload, header->digest, 1); + if (ret) + goto error; + + memcpy(header->blob, payload->blob, payload->blob_len); + header->blob_len = payload->blob_len; + +error: + key_revoke(key); + key_put(key); +reset: + kfree(digests); + tpm_pcr_reset(chip, 23); + return ret; +} + +int swsusp_decrypt_digest(struct swsusp_header *header) +{ + const struct cred *cred = current_cred(); + char *keytemplate = "load\t%s\tkeyhandle=0x81000001"; + struct trusted_key_payload *payload; + struct tpm_digest *digests = NULL; + char *blobstring = NULL; + char *keyinfo = NULL; + struct tpm_chip *chip; + struct key *key; + int i, ret; + + chip = tpm_default_chip(); + + if (!chip) + return -ENODEV; + + if (!(tpm_is_tpm2(chip))) + return -ENODEV; + + ret = tpm_pcr_reset(chip, 23); + if (ret != 0) + return ret; + + digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest), + GFP_KERNEL); + if (!digests) + goto reset; + + for (i = 0; i <= chip->nr_allocated_banks; i++) { + digests[i].alg_id = chip->allocated_banks[i].alg_id; + if (digests[i].alg_id == digest.alg_id) + memcpy(&digests[i], &digest, sizeof(digest)); + } + + ret = tpm_pcr_extend(chip, 23, digests); + if (ret != 0) + goto reset; + + blobstring = kmalloc(header->blob_len * 2, GFP_KERNEL); + if (!blobstring) { + ret = -ENOMEM; + goto reset; + } + + bin2hex(blobstring, header->blob, header->blob_len); + + keyinfo = kasprintf(GFP_KERNEL, keytemplate, blobstring); + if (!keyinfo) { + ret = -ENOMEM; + goto reset; + } + + key = key_alloc(&key_type_trusted, "swsusp", GLOBAL_ROOT_UID, + GLOBAL_ROOT_GID, cred, 0, KEY_ALLOC_NOT_IN_QUOTA, + NULL); + + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto out; + } + + ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL, + NULL); + if (ret < 0) + goto out; + + payload = key->payload.data[0]; + + ret = swsusp_enc_dec(payload, header->digest, 0); + +out: + key_revoke(key); + key_put(key); +reset: + kfree(keyinfo); + kfree(blobstring); + kfree(digests); + tpm_pcr_reset(chip, 23); + return ret; +} + +int swsusp_digest_setup(struct swap_map_handle *handle) +{ + struct crypto_shash *tfm; + int ret; + + tfm = crypto_alloc_shash("sha256", 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + handle->desc = kmalloc(sizeof(struct shash_desc) + + crypto_shash_descsize(tfm), GFP_KERNEL); + if (!handle->desc) { + crypto_free_shash(tfm); + return -ENOMEM; + } + + handle->desc->tfm = tfm; + ret = crypto_shash_init(handle->desc); + if (ret != 0) { + crypto_free_shash(tfm); + kfree(handle->desc); + return ret; + } + + return 0; +} + +void swsusp_digest_update(struct swap_map_handle *handle, char *buf, + size_t size) +{ + crypto_shash_update(handle->desc, buf, size); +} + +void swsusp_digest_final(struct swap_map_handle *handle) +{ + crypto_shash_final(handle->desc, handle->digest); + crypto_free_shash(handle->desc->tfm); + kfree(handle->desc); +} + +int secure_hibernation_available(void) +{ + struct tpm_chip *chip = tpm_default_chip(); + + if (!chip) + return -ENODEV; + + if (!(tpm_is_tpm2(chip))) + return -ENODEV; + + return 0; +} diff --git a/kernel/power/tpm.h b/kernel/power/tpm.h new file mode 100644 index 000000000000..75b9140e5dc2 --- /dev/null +++ b/kernel/power/tpm.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include "swap.h" + +#ifndef _POWER_TPM_H +#define _POWER_TPM_H + +#ifdef CONFIG_SECURE_HIBERNATION +int secure_hibernation_available(void); +int swsusp_encrypt_digest(struct swsusp_header *header); +int swsusp_decrypt_digest(struct swsusp_header *header); +int swsusp_digest_setup(struct swap_map_handle *handle); +void swsusp_digest_update(struct swap_map_handle *handle, char *buf, + size_t size); +void swsusp_digest_final(struct swap_map_handle *handle); +#else +static inline int secure_hibernation_available(void) +{ + return -ENODEV; +}; +static inline int swsusp_encrypt_digest(struct swsusp_header *header) +{ + return 0; +} +static inline int swsusp_decrypt_digest(struct swsusp_header *header) +{ + return 0; +} +static inline int swsusp_digest_setup(struct swap_map_handle *handle) +{ + return 0; +} +static inline void swsusp_digest_update(struct swap_map_handle *handle, + char *buf, size_t size) {}; +static inline void swsusp_digest_final(struct swap_map_handle *handle) {}; +#endif + +#endif /* _POWER_TPM_H */ -- 2.30.0.617.g56c4b15f3c-goog