From patchwork Sat Feb 20 01:32:47 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matthew Garrett X-Patchwork-Id: 385441 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.3 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT, USER_IN_DEF_DKIM_WL autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2FDCEC433E6 for ; Sat, 20 Feb 2021 01:34:45 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 00A0E64EDE for ; Sat, 20 Feb 2021 01:34:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229884AbhBTBe2 (ORCPT ); Fri, 19 Feb 2021 20:34:28 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44172 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229771AbhBTBe1 (ORCPT ); Fri, 19 Feb 2021 20:34:27 -0500 Received: from mail-qt1-x84a.google.com (mail-qt1-x84a.google.com [IPv6:2607:f8b0:4864:20::84a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id AF0D8C061794 for ; Fri, 19 Feb 2021 17:33:09 -0800 (PST) Received: by mail-qt1-x84a.google.com with SMTP id d10so4441963qtx.8 for ; Fri, 19 Feb 2021 17:33:09 -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=CtLRPHa6vVfWJfnUvKFAfqInMZ/2zkbzy/KaLuvq/RY=; b=np6G+qmB02m6CtsdX1J98BauvoCRqwR9uUAoQZ22HnNJ8AErOq7qAe01yv81qnazq7 7bmJqaZaEgG8ceyd+8a5WTsKEgcbxIK2fYrKz41nff+TJH3yWqdUMkWWFx62d5DLgJqZ n5/KdFSRNpxtx0Q+UEJQxrJfHUm3F4XGFEhSRwlZeLop/Ez9xYlluW4mTSJE8FE97q1v cosSPTLjucB4Jk1Cyy3x0f/w0r7Fv4vja9ht+0qyAIOFidMadt/oE6GmN2Wx24XSpQcx 62/kE5HdcY/YJL1Q5kiK5ftf3dCNwHukTsdRn8vzZZHFcU/3+A1jYEvb7iboCMpeH5L7 X+uQ== 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=CtLRPHa6vVfWJfnUvKFAfqInMZ/2zkbzy/KaLuvq/RY=; b=lz8zfc9Wdi3prLa63ZaR/k/OvzL6KzBy1sQXk9cgvO3Jq/byC6/HinPkw8VSmpq+Q9 cohxywG50483vkBsg4u45e1IFd36AP861lLTwH+2i4aNtPrYxIdLg4rDOCajSZnqR92U WW49+bSzh5kTxomev066ZbxSs6S7XucVD1RSoLyxrRtXA0foKp72rpaR7vseFO19K/BF yy/ESGTg0C7MGuUu2j0Za8C5kHICWP6ffzkjhUhpvBT/5IZLbPT/aG7UMDS9WqJdLaHZ ICLjvFSKcXJyjRemL/GkF+vynWwTMvtmfQ8/t4aJB3+22UiywIj9RLcz6pzZhEq0UzVX wT7A== X-Gm-Message-State: AOAM531hzBabLBeQVQf5lgeQXL762fk9aEml3yDlvLvC40sOXPTOwCdV jGl00FvSd8a+qrHaS9RoFTOWP9KkpfBGaKntDK+QWQ== X-Google-Smtp-Source: ABdhPJxgycPtcGyLtpEWUmPPRqEvf5yxoDeFB4QBUiNxo/8m3G60P8mXpFrAXLh1D4foiHWOPPTgCdpjOwOooBtVjqzOIQ== 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:a0c:ac43:: with SMTP id m3mr12016912qvb.37.1613784787904; Fri, 19 Feb 2021 17:33:07 -0800 (PST) Date: Sat, 20 Feb 2021 01:32:47 +0000 In-Reply-To: <20210220013255.1083202-1-matthewgarrett@google.com> Message-Id: <20210220013255.1083202-2-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 1/9] tpm: Add support for in-kernel resetting of PCRs 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 Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org Add an internal command for resetting a PCR. Signed-off-by: Matthew Garrett --- drivers/char/tpm/tpm-interface.c | 28 +++++++++++++++++++++++++ drivers/char/tpm/tpm.h | 2 ++ drivers/char/tpm/tpm1-cmd.c | 34 ++++++++++++++++++++++++++++++ drivers/char/tpm/tpm2-cmd.c | 36 ++++++++++++++++++++++++++++++++ include/linux/tpm.h | 7 +++++++ 5 files changed, 107 insertions(+) diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index 1621ce818705..17b8643ee109 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -342,6 +342,34 @@ int tpm_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, } EXPORT_SYMBOL_GPL(tpm_pcr_extend); +/** + * tpm_pcr_reset - reset the specified PCR + * @chip: a &struct tpm_chip instance, %NULL for the default chip + * @pcr_idx: the PCR to be reset + * + * Return: same as with tpm_transmit_cmd() + */ +int tpm_pcr_reset(struct tpm_chip *chip, u32 pcr_idx) +{ + int rc; + + chip = tpm_find_get_ops(chip); + if (!chip) + return -ENODEV; + + if (chip->flags & TPM_CHIP_FLAG_TPM2) { + rc = tpm2_pcr_reset(chip, pcr_idx); + goto out; + } + + rc = tpm1_pcr_reset(chip, pcr_idx, "attempting to reset a PCR"); + +out: + tpm_put_ops(chip); + return rc; +} +EXPORT_SYMBOL_GPL(tpm_pcr_reset); + /** * tpm_send - send a TPM command * @chip: a &struct tpm_chip instance, %NULL for the default chip diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index 947d1db0a5cc..746f7696bdc0 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -176,6 +176,7 @@ int tpm1_get_timeouts(struct tpm_chip *chip); unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal); int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash, const char *log_msg); +int tpm1_pcr_reset(struct tpm_chip *chip, u32 pcr_idx, const char *log_msg); int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf); ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, const char *desc, size_t min_cap_length); @@ -220,6 +221,7 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx, struct tpm_digest *digest, u16 *digest_size_ptr); int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, struct tpm_digest *digests); +int tpm2_pcr_reset(struct tpm_chip *chip, u32 pcr_idx); int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max); ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id, u32 *value, const char *desc); diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c index ca7158fa6e6c..36990e9d2dc1 100644 --- a/drivers/char/tpm/tpm1-cmd.c +++ b/drivers/char/tpm/tpm1-cmd.c @@ -478,6 +478,40 @@ int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash, return rc; } +struct tpm_pcr_selection { + u16 size_of_select; + u8 pcr_select[3]; +} __packed; + +#define TPM_ORD_PCR_RESET 200 +int tpm1_pcr_reset(struct tpm_chip *chip, u32 pcr_idx, const char *log_msg) +{ + struct tpm_pcr_selection selection; + struct tpm_buf buf; + int i, rc; + char tmp; + + rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_RESET); + if (rc) + return rc; + + selection.size_of_select = 3; + + for (i = 0; i < selection.size_of_select; i++) { + tmp = 0; + if (pcr_idx / 3 == i) { + pcr_idx -= i * 8; + tmp |= 1 << pcr_idx; + } + selection.pcr_select[i] = tmp; + } + tpm_buf_append(&buf, (u8 *)&selection, sizeof(selection)); + + rc = tpm_transmit_cmd(chip, &buf, sizeof(selection), log_msg); + tpm_buf_destroy(&buf); + return rc; +} + #define TPM_ORD_GET_CAP 101 ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, const char *desc, size_t min_cap_length) diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index eff1f12d981a..9609ae8086c6 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -269,6 +269,42 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, return rc; } +/** + * tpm2_pcr_reset() - reset a PCR + * + * @chip: TPM chip to use. + * @pcr_idx: index of the PCR. + * + * Return: Same as with tpm_transmit_cmd. + */ +int tpm2_pcr_reset(struct tpm_chip *chip, u32 pcr_idx) +{ + struct tpm_buf buf; + struct tpm2_null_auth_area auth_area; + int rc; + + rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_RESET); + if (rc) + return rc; + + tpm_buf_append_u32(&buf, pcr_idx); + + auth_area.handle = cpu_to_be32(TPM2_RS_PW); + auth_area.nonce_size = 0; + auth_area.attributes = 0; + auth_area.auth_size = 0; + + tpm_buf_append_u32(&buf, sizeof(struct tpm2_null_auth_area)); + tpm_buf_append(&buf, (const unsigned char *)&auth_area, + sizeof(auth_area)); + + rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to reset a PCR"); + + tpm_buf_destroy(&buf); + + return rc; +} + struct tpm2_get_random_out { __be16 size; u8 buffer[TPM_MAX_RNG_DATA]; diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 8f4ff39f51e7..e2075e2242a0 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -211,6 +211,7 @@ enum tpm2_command_codes { TPM2_CC_HIERARCHY_CONTROL = 0x0121, TPM2_CC_HIERARCHY_CHANGE_AUTH = 0x0129, TPM2_CC_CREATE_PRIMARY = 0x0131, + TPM2_CC_PCR_RESET = 0x013D, TPM2_CC_SEQUENCE_COMPLETE = 0x013E, TPM2_CC_SELF_TEST = 0x0143, TPM2_CC_STARTUP = 0x0144, @@ -399,6 +400,7 @@ static inline u32 tpm2_rc_value(u32 rc) extern int tpm_is_tpm2(struct tpm_chip *chip); extern int tpm_pcr_read(struct tpm_chip *chip, u32 pcr_idx, struct tpm_digest *digest); +extern int tpm_pcr_reset(struct tpm_chip *chip, u32 pcr_idx); extern int tpm_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, struct tpm_digest *digests); extern int tpm_send(struct tpm_chip *chip, void *cmd, size_t buflen); @@ -417,6 +419,11 @@ static inline int tpm_pcr_read(struct tpm_chip *chip, int pcr_idx, return -ENODEV; } +static inline int tpm_pcr_reset(struct tpm_chip *chip, int pcr_idx) +{ + return -ENODEV; +} + static inline int tpm_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, struct tpm_digest *digests) { From patchwork Sat Feb 20 01:32:48 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matthew Garrett X-Patchwork-Id: 385440 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.3 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT,USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4D5DEC433E9 for ; Sat, 20 Feb 2021 01:34:45 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 280DA64EE9 for ; Sat, 20 Feb 2021 01:34:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229889AbhBTBea (ORCPT ); Fri, 19 Feb 2021 20:34:30 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44180 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229868AbhBTBe2 (ORCPT ); Fri, 19 Feb 2021 20:34:28 -0500 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 59AFFC0617A9 for ; Fri, 19 Feb 2021 17:33:10 -0800 (PST) Received: by mail-yb1-xb4a.google.com with SMTP id v196so8820602ybv.3 for ; Fri, 19 Feb 2021 17:33:10 -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=y0augfXTryPPckNIjyG37nVNdhig6ZbrGk3G+Se0bhk=; b=RAnSVHS6Smj0l3LTFE/vx6JcAMiDFRCoCBnWUB8vImivgo1DTitkKgcGHqMjwAJw/O txIgPqjTqa5i4y7plNhFKxAOjXZ8FqAPcnC1jxvxhEqDXMk3kcqnC0fP9TLI25gVJUXK ddHY50qffEoya6LEsxV2vjOe6YrzC22XhdLQxxrKho7+T0KrN7kp1rjJhsyKoU/cKfYb TEn1URRMkGXIyknaMegtLk6tmlOfKyVP5pr7+ftAFXeT0hi4EscWeLB1AHcTBFFNiuzt LWSmztlO4C9ylDtqEpai3Jw11CILCmSXimM0qGQkwtnWxlfXul5qzttJBxAyTWmez48Z zDyQ== 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=y0augfXTryPPckNIjyG37nVNdhig6ZbrGk3G+Se0bhk=; b=IjxFF0ekjhXYLdXDLRC0mVMGaDrC9Tiu8bjn+qHRGuM+lQfxiuKq+EvWwpqyWnLQi2 Bz8iwqKyL5YahyArmAtfYHejSq1APtkt+6fROUHRYFUCZTlWhF3oZ7dlDpazH041+SqP v0y+5m1gyYXbRqJguAvywR5fishHjNDrHD7rb3jFdhJJIR0L2VJZpsx1bFfMDQem3KKD mNM5FH8i9aV98zJmOE752AjfKSD/rkc13U1ig4XOgQNaMq2w9fJB3hqp12k68fdZ7wJr O0nM8HPqJOaTul2jTMlvbFQ85yQRpGUFuIPHWq9pvBrd+ZqJdV/UjbGP50wY5k9Dw6mA z32g== X-Gm-Message-State: AOAM532ITcrNs5X1HPO6NkL5g9EfkI9kipMcyT1vhxbC1TXaFMw3i76b vM2LgJwjaJU6Hd2fk1esO/L9bW9H37FrIlOy71/ibg== X-Google-Smtp-Source: ABdhPJytRkNN3Yr0iLNpnA3MzM1hsHO7gs7Uv3eNdAOwxj6Y/9Z3hSrHrL2ZuhfAoEO1BriPoncl5gzOqTYtEKVrKpRP2A== 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:9383:: with SMTP id a3mr17879351ybm.215.1613784789563; Fri, 19 Feb 2021 17:33:09 -0800 (PST) Date: Sat, 20 Feb 2021 01:32:48 +0000 In-Reply-To: <20210220013255.1083202-1-matthewgarrett@google.com> Message-Id: <20210220013255.1083202-3-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 2/9] tpm: Allow PCR 23 to be restricted to kernel-only use 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 Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org Under certain circumstances it might be desirable to enable the creation of TPM-backed secrets that are only accessible to the kernel. In an ideal world this could be achieved by using TPM localities, but these don't appear to be available on consumer systems. An alternative is to simply block userland from modifying one of the resettable PCRs, leaving it available to the kernel. If the kernel ensures that no userland can access the TPM while it is carrying out work, it can reset PCR 23, extend it to an arbitrary value, create or load a secret, and then reset the PCR again. Even if userland somehow obtains the sealed material, it will be unable to unseal it since PCR 23 will never be in the appropriate state. Signed-off-by: Matthew Garrett --- drivers/char/tpm/Kconfig | 10 +++++++++ drivers/char/tpm/tpm-dev-common.c | 8 +++++++ drivers/char/tpm/tpm.h | 21 +++++++++++++++++++ drivers/char/tpm/tpm1-cmd.c | 35 +++++++++++++++++++++++++++++++ drivers/char/tpm/tpm2-cmd.c | 22 +++++++++++++++++++ drivers/char/tpm/tpm2-space.c | 2 +- 6 files changed, 97 insertions(+), 1 deletion(-) diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig index a18c314da211..bba30fb16a2e 100644 --- a/drivers/char/tpm/Kconfig +++ b/drivers/char/tpm/Kconfig @@ -190,4 +190,14 @@ config TCG_FTPM_TEE This driver proxies for firmware TPM running in TEE. source "drivers/char/tpm/st33zp24/Kconfig" + +config TCG_TPM_RESTRICT_PCR + bool "Restrict userland access to PCR 23" + depends on TCG_TPM + help + If set, block userland from extending or resetting PCR 23. This + allows it to be restricted to in-kernel use, preventing userland + from being able to make use of data sealed to the TPM by the kernel. + This is required for secure hibernation support, but should be left + disabled if any userland may require access to PCR23. endif # TCG_TPM diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c index 1784530b8387..d3db4fd76257 100644 --- a/drivers/char/tpm/tpm-dev-common.c +++ b/drivers/char/tpm/tpm-dev-common.c @@ -193,6 +193,14 @@ ssize_t tpm_common_write(struct file *file, const char __user *buf, priv->response_read = false; *off = 0; + if (priv->chip->flags & TPM_CHIP_FLAG_TPM2) + ret = tpm2_cmd_restricted(priv->chip, priv->data_buffer, size); + else + ret = tpm1_cmd_restricted(priv->chip, priv->data_buffer, size); + + if (ret) + goto out; + /* * If in nonblocking mode schedule an async job to send * the command return the size. diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index 746f7696bdc0..8eed5016d733 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -232,6 +232,8 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type); unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal); int tpm2_probe(struct tpm_chip *chip); int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip); +int tpm_find_and_validate_cc(struct tpm_chip *chip, struct tpm_space *space, + const void *buf, size_t bufsiz); int tpm2_find_cc(struct tpm_chip *chip, u32 cc); int tpm2_init_space(struct tpm_space *space, unsigned int buf_size); void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space); @@ -245,4 +247,23 @@ void tpm_bios_log_setup(struct tpm_chip *chip); void tpm_bios_log_teardown(struct tpm_chip *chip); int tpm_dev_common_init(void); void tpm_dev_common_exit(void); + +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR +#define TPM_RESTRICTED_PCR 23 + +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size); +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size); +#else +static inline int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, + size_t size) +{ + return 0; +} + +static inline int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, + size_t size) +{ + return 0; +} +#endif #endif diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c index 36990e9d2dc1..2dab1647d89c 100644 --- a/drivers/char/tpm/tpm1-cmd.c +++ b/drivers/char/tpm/tpm1-cmd.c @@ -840,3 +840,38 @@ int tpm1_get_pcr_allocation(struct tpm_chip *chip) return 0; } + +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR +int tpm1_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size) +{ + struct tpm_header *header = (struct tpm_header *)buffer; + char len, offset; + u32 *pcr; + int pos; + + switch (be32_to_cpu(header->ordinal)) { + case TPM_ORD_PCR_EXTEND: + if (size < (TPM_HEADER_SIZE + sizeof(u32))) + return -EINVAL; + pcr = (u32 *)&buffer[TPM_HEADER_SIZE]; + if (be32_to_cpu(*pcr) == TPM_RESTRICTED_PCR) + return -EPERM; + break; + case TPM_ORD_PCR_RESET: + if (size < (TPM_HEADER_SIZE + 1)) + return -EINVAL; + len = buffer[TPM_HEADER_SIZE]; + if (size < (TPM_HEADER_SIZE + 1 + len)) + return -EINVAL; + offset = TPM_RESTRICTED_PCR/3; + if (len < offset) + break; + pos = TPM_HEADER_SIZE + 1 + offset; + if (buffer[pos] & (1 << (TPM_RESTRICTED_PCR - 2 * offset))) + return -EPERM; + break; + } + + return 0; +} +#endif diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 9609ae8086c6..7dbd4590dee8 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -795,3 +795,25 @@ int tpm2_find_cc(struct tpm_chip *chip, u32 cc) return -1; } + +#ifdef CONFIG_TCG_TPM_RESTRICT_PCR +int tpm2_cmd_restricted(struct tpm_chip *chip, u8 *buffer, size_t size) +{ + int cc = tpm_find_and_validate_cc(chip, NULL, buffer, size); + u32 *handle; + + switch (cc) { + case TPM2_CC_PCR_EXTEND: + case TPM2_CC_PCR_RESET: + if (size < (TPM_HEADER_SIZE + sizeof(u32))) + return -EINVAL; + + handle = (u32 *)&buffer[TPM_HEADER_SIZE]; + if (be32_to_cpu(*handle) == TPM_RESTRICTED_PCR) + return -EPERM; + break; + } + + return 0; +} +#endif diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c index 784b8b3cb903..76a993492962 100644 --- a/drivers/char/tpm/tpm2-space.c +++ b/drivers/char/tpm/tpm2-space.c @@ -262,7 +262,7 @@ static int tpm2_map_command(struct tpm_chip *chip, u32 cc, u8 *cmd) return 0; } -static int tpm_find_and_validate_cc(struct tpm_chip *chip, +int tpm_find_and_validate_cc(struct tpm_chip *chip, struct tpm_space *space, const void *cmd, size_t len) { From patchwork Sat Feb 20 01:32:53 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matthew Garrett X-Patchwork-Id: 385437 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.3 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT, USER_IN_DEF_DKIM_WL autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id D451DC433E0 for ; Sat, 20 Feb 2021 01:35:51 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 9F15E64EE5 for ; Sat, 20 Feb 2021 01:35:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230060AbhBTBff (ORCPT ); Fri, 19 Feb 2021 20:35:35 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44350 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230018AbhBTBfK (ORCPT ); Fri, 19 Feb 2021 20:35:10 -0500 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A86DCC0611BD for ; Fri, 19 Feb 2021 17:33:18 -0800 (PST) Received: by mail-yb1-xb4a.google.com with SMTP id x4so8800147ybj.22 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=A9zBaBvdB/dXS/QqtVz5vGSWwZQV/sWapIlQmURRcUZIQgPRyptyhkDMLdoplhJDgD tt6nfBtsafFMXZc4XdyHUp79o3XCPvZo3dXQYKiYeRe9xrG2PfXeSb7ECJWFZytNYSdE iSqaXNK7tBGHRrY7NKFa5ejr6OH1sFAj3lL4JISB/VHCl9jIrERKCoxmjJ3X8evGL21D Jo59TGpUTurmDF8ySdq4qFVKLlGyoyYQodKXGyUXjwTf97HZopd2wNy2BgF1pkwoa2aO 7j2c2Vi9lnyxYBvRhOet5RdGQYxbf5X6lGuqyLmbVy4wfk6soiwe9OIPF0ikXRMRC6g0 YMqQ== X-Gm-Message-State: AOAM530P1Tge3E9cg2bXS6EvN+Hfrbz3Daug49nYrEB+zjjml+PaRtl8 XlQWODspM6hk8T/Ms28VtgPdy1m0qD9u3nuiP2BAhw== X-Google-Smtp-Source: ABdhPJwoq5Xgs9mVLQPRLuL0fs+U1jl6Dz4XXx/0DoMGONnIQm4b8mNyDg4/g6CBwb67UiEgZB39EuO0+kA9Vwt6DawgCQ== 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 Precedence: bulk List-ID: X-Mailing-List: linux-pm@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 */ From patchwork Sat Feb 20 01:32:54 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matthew Garrett X-Patchwork-Id: 385438 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.3 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT, USER_IN_DEF_DKIM_WL autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id F025BC43381 for ; Sat, 20 Feb 2021 01:35:51 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D81CF64E77 for ; Sat, 20 Feb 2021 01:35:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230019AbhBTBfh (ORCPT ); Fri, 19 Feb 2021 20:35:37 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44362 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230021AbhBTBfM (ORCPT ); Fri, 19 Feb 2021 20:35:12 -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 3322AC061A2B for ; Fri, 19 Feb 2021 17:33:20 -0800 (PST) Received: by mail-yb1-xb49.google.com with SMTP id u1so8818051ybu.14 for ; Fri, 19 Feb 2021 17:33:20 -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=Le2xy86M2g7vFjnuX/RXj2Z9OeFij6CrkPXndbL+3YM=; b=mUmzPMLQ2EhM5ohqQmvRX3zzjTI4HWgSEU8OF6ChUOqqCnyRmUWuS7Fg85iVRVC6D0 tlS1Qf9auwUtImh2HiRyyIF9zKTBlptAOolf0FoHdiiUmi0OW/MbiiVc5aFxLp0Ixvg1 cFeCJz9zZMvDoZq8E4OJNEyCKW06WEsbbi1WGGYwxmWGValJTlOySL2JTQ+lyT9DZEcG H2uY8kjDRo7fTGbtIHU0o77/si0NRfV4D/hT8to4ifU0A4zu8NUO1F1mRowKzzBwornS TsK6HQUR5c3H2o4BOGqBx1XATTaJyYRtgg6nD/nS2R0cTSOvXH5a/+N4HO8Uwo08O0Ik vzfQ== 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=Le2xy86M2g7vFjnuX/RXj2Z9OeFij6CrkPXndbL+3YM=; b=AXFxqVMiKwkYjCDMRZlntzXN91aUjaZhniGUHf9yY5jBoUSOESvYzOpkp2/zQ7/a3M 1kF4A/IXevN02R7m5DlTDFc2hRl5STBfvZMnYMyDIBRqtyToF3yuU/V90BwyW0mEi7dV wr6nstSdZ/nMrnhsKRpfSh4CA1nzEuy4k8+fIfYZ6mCQlwxTE844+kmSmUAM5awsjpUU wTLngfAfdZ96NT+J2dzt9y638YvxYq4k6xJzg2IlzaStkuCxRo2phl+qxfXq1n/pqvkd Zdp4hl+ychII5ebh5DjDC3apKMqwbNy8B4ba1zimeSCfY1kTgwyIIXO7pnYo0VacRYt4 PhXg== X-Gm-Message-State: AOAM530RtBkJtVUC+iHbBYb+ehd9VtXopR1Z8H+oYsZvrYxkdNz/a8oK hloaLqSQJlLqpS0nIV3czQwb11yY1uRs4YXdhBX+HQ== X-Google-Smtp-Source: ABdhPJz+znzmydksCXNSz0qRUgqJOeUsiHxIZRQcS35YoWwljFjdXh5jNWJo2zOzCx4tGEmcAtw9+Y046tcbzA8xOA1QIg== 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:a0c9:: with SMTP id i9mr15696415ybm.479.1613784799467; Fri, 19 Feb 2021 17:33:19 -0800 (PST) Date: Sat, 20 Feb 2021 01:32:54 +0000 In-Reply-To: <20210220013255.1083202-1-matthewgarrett@google.com> Message-Id: <20210220013255.1083202-9-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 8/9] pm: hibernate: Verify the digest encryption key 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 Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org We want to ensure that the key used to encrypt the digest was created by the kernel during hibernation. To do this we request that the TPM include information about the value of PCR 23 at the time of key creation in the sealed blob. On resume, we can ask the TPM to certify that the creation data is accurate and then make sure that the PCR information in the blob corresponds to the expected value. Since only the kernel can touch PCR 23, if an attacker generates a key themselves the value of PCR 23 will have been different, allowing us to reject the key and boot normally instead of resuming. Signed-off-by: Matthew Garrett --- include/linux/tpm.h | 1 + kernel/power/tpm.c | 150 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/include/linux/tpm.h b/include/linux/tpm.h index e2075e2242a0..f6970986b097 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -216,6 +216,7 @@ enum tpm2_command_codes { TPM2_CC_SELF_TEST = 0x0143, TPM2_CC_STARTUP = 0x0144, TPM2_CC_SHUTDOWN = 0x0145, + TPM2_CC_CERTIFYCREATION = 0x014A, TPM2_CC_NV_READ = 0x014E, TPM2_CC_CREATE = 0x0153, TPM2_CC_LOAD = 0x0157, diff --git a/kernel/power/tpm.c b/kernel/power/tpm.c index 953dcbdc56d8..34e6cfb98ce4 100644 --- a/kernel/power/tpm.c +++ b/kernel/power/tpm.c @@ -14,6 +14,12 @@ static struct tpm_digest digest = { .alg_id = TPM_ALG_SHA256, 0xf1, 0x22, 0x38, 0x6c, 0x33, 0xb1, 0x14, 0xb7, 0xec, 0x05, 0x5f, 0x49}}; +/* sha256(sha256(empty_pcr | digest)) */ +static char expected_digest[] = {0x2f, 0x96, 0xf2, 0x1b, 0x70, 0xa9, 0xe8, + 0x42, 0x25, 0x8e, 0x66, 0x07, 0xbe, 0xbc, 0xe3, 0x1f, 0x2c, 0x84, 0x4a, + 0x3f, 0x85, 0x17, 0x31, 0x47, 0x9a, 0xa5, 0x53, 0xbb, 0x23, 0x0c, 0x32, + 0xf3}; + struct skcipher_def { struct scatterlist sg; struct crypto_skcipher *tfm; @@ -21,6 +27,39 @@ struct skcipher_def { struct crypto_wait wait; }; +static int sha256_data(char *buf, int size, char *output) +{ + struct crypto_shash *tfm; + struct shash_desc *desc; + int ret; + + tfm = crypto_alloc_shash("sha256", 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + desc = kmalloc(sizeof(struct shash_desc) + + crypto_shash_descsize(tfm), GFP_KERNEL); + if (!desc) { + crypto_free_shash(tfm); + return -ENOMEM; + } + + desc->tfm = tfm; + ret = crypto_shash_init(desc); + if (ret != 0) { + crypto_free_shash(tfm); + kfree(desc); + return ret; + } + + crypto_shash_update(desc, buf, size); + crypto_shash_final(desc, output); + crypto_free_shash(desc->tfm); + kfree(desc); + + return 0; +} + static int swsusp_enc_dec(struct trusted_key_payload *payload, char *buf, int enc) { @@ -86,6 +125,58 @@ static int swsusp_enc_dec(struct trusted_key_payload *payload, char *buf, return ret; } +static int tpm_certify_creationdata(struct tpm_chip *chip, + struct trusted_key_payload *payload) +{ + struct tpm_header *head; + struct tpm_buf buf; + int rc; + + rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CERTIFYCREATION); + if (rc) + return rc; + + /* Use TPM_RH_NULL for signHandle */ + tpm_buf_append_u32(&buf, 0x40000007); + + /* Object handle */ + tpm_buf_append_u32(&buf, payload->blob_handle); + + /* Auth */ + tpm_buf_append_u32(&buf, 9); + tpm_buf_append_u32(&buf, TPM2_RS_PW); + tpm_buf_append_u16(&buf, 0); + tpm_buf_append_u8(&buf, 0); + tpm_buf_append_u16(&buf, 0); + + /* Qualifying data */ + tpm_buf_append_u16(&buf, 0); + + /* Creation data hash */ + tpm_buf_append_u16(&buf, payload->creation_hash_len); + tpm_buf_append(&buf, payload->creation_hash, + payload->creation_hash_len); + + /* signature scheme */ + tpm_buf_append_u16(&buf, TPM_ALG_NULL); + + /* creation ticket */ + tpm_buf_append(&buf, payload->tk, payload->tk_len); + + rc = tpm_send(chip, buf.data, tpm_buf_length(&buf)); + if (rc) + goto out; + + head = (struct tpm_header *)buf.data; + + if (head->return_code != 0) + rc = -EINVAL; +out: + tpm_buf_destroy(&buf); + + return rc; +} + int swsusp_encrypt_digest(struct swsusp_header *header) { const struct cred *cred = current_cred(); @@ -95,7 +186,7 @@ int swsusp_encrypt_digest(struct swsusp_header *header) struct key *key; int ret, i; - char *keyinfo = "new\t32\tkeyhandle=0x81000001"; + char *keyinfo = "new\t32\tkeyhandle=0x81000001\tcreationpcrs=0x00800000"; chip = tpm_default_chip(); @@ -164,6 +255,7 @@ int swsusp_decrypt_digest(struct swsusp_header *header) char *keytemplate = "load\t%s\tkeyhandle=0x81000001"; struct trusted_key_payload *payload; struct tpm_digest *digests = NULL; + char certhash[SHA256_DIGEST_SIZE]; char *blobstring = NULL; char *keyinfo = NULL; struct tpm_chip *chip; @@ -184,8 +276,10 @@ int swsusp_decrypt_digest(struct swsusp_header *header) digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest), GFP_KERNEL); - if (!digests) + 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; @@ -227,8 +321,58 @@ int swsusp_decrypt_digest(struct swsusp_header *header) payload = key->payload.data[0]; - ret = swsusp_enc_dec(payload, header->digest, 0); + ret = sha256_data(payload->creation, payload->creation_len, certhash); + if (ret < 0) + goto out; + + if (memcmp(payload->creation_hash, certhash, SHA256_DIGEST_SIZE) != 0) { + ret = -EINVAL; + goto out; + } + + ret = tpm_certify_creationdata(chip, payload); + if (ret != 0) { + ret = -EINVAL; + goto out; + } + + /* We now know that the creation data is authentic - parse it */ + + /* TPML_PCR_SELECTION.count */ + if (be32_to_cpu(*(int *)payload->creation) != 1) { + ret = -EINVAL; + goto out; + } + + if (be16_to_cpu(*(u16 *)&payload->creation[4]) != TPM_ALG_SHA256) { + ret = -EINVAL; + goto out; + } + + if (*(char *)&payload->creation[6] != 3) { + ret = -EINVAL; + goto out; + } + + /* PCR 23 selected */ + if (be32_to_cpu(*(int *)&payload->creation[6]) != 0x03000080) { + ret = -EINVAL; + goto out; + } + + if (be16_to_cpu(*(u16 *)&payload->creation[10]) != + SHA256_DIGEST_SIZE) { + ret = -EINVAL; + goto out; + } + if (memcmp(&payload->creation[12], expected_digest, + SHA256_DIGEST_SIZE) != 0) { + ret = -EINVAL; + goto out; + } + + ret = swsusp_enc_dec(payload, header->digest, 0); out: key_revoke(key); key_put(key); From patchwork Sat Feb 20 01:32:55 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matthew Garrett X-Patchwork-Id: 385439 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.3 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT,USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 13304C433E6 for ; Sat, 20 Feb 2021 01:35:31 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id DDBA464E77 for ; Sat, 20 Feb 2021 01:35:30 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230024AbhBTBfM (ORCPT ); Fri, 19 Feb 2021 20:35:12 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44172 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229996AbhBTBfE (ORCPT ); Fri, 19 Feb 2021 20:35:04 -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 DD784C061A2E for ; Fri, 19 Feb 2021 17:33:21 -0800 (PST) Received: by mail-yb1-xb49.google.com with SMTP id u17so8910099ybi.10 for ; Fri, 19 Feb 2021 17:33:21 -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=AHjbHsJdHsjDWlT0meS/7wKVqgJA5qMsPwUMgz7CTrY=; b=DPjRo4j75pe5NPWUZw2PULq38PKScJgLe5C694z+Od25e1mgQhQ+0ZXITV67hlasB7 BHs9xe+ceb9RhAHRmdX19BKDLmd5FIXfgd8HemfRm4tTBBBAbKdirqtCV3bLbCZ6fMgH Fx4DgvBfX2gig6RPjueqXD+IlBOr8gwiqtvTUgGre0W6u7Vgp1DYhkYrStASE96rNlLp 1+kNlWDHPnwUYBF4nGUn+mChnYkhsFYkxPUUGh4bK1qbtcGhqDDiGUFIPRRT2WDqRi4T MtI3kHh6oBvhW7sOheb/oGp2WKxKuMWh9Rk58TkctHC7Apr/uCRNteVL4cLuYcZAVg/H 126Q== 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=AHjbHsJdHsjDWlT0meS/7wKVqgJA5qMsPwUMgz7CTrY=; b=Z2mmFjb3OyMoUFSBk4ZK5h/ofJnr/+WcZOE4X2xMeXDpYh/Q/56f04NQpVgFOinspu J7zbrMb2KrnhJI+N2YLPV/eU4Pb7YlrRTBSdoyyqO98yFX8e5MG9iZyy5woJBL3cQkbv dwTNHBsfaQ2dAINxxKBZgKNyXIy9KeRWP/MSX61TU4Ti7FsojrSyg6LhTxMpUILK93Sx 7/wet+KmjTDLss7crhU59e1cAzbDSW2Jj4pLUO+CX2FCTwqDDqofGbKXoHasrvgvERXo /tX/c554E21W4Cz/p0fCwV5MvsQteTAuZiIA737o3PBurf+j1Tq7OVEXf9oAqL7RTFC0 XVlQ== X-Gm-Message-State: AOAM5300Q7wuOzY38LrvgYwWFSO9UmP6sQFjlDywvDuj1X2K/WNlfQlc v+bhMHQ2QuAC2z3nyhUcP3XHiw8AZU7wMnlxJ45g8g== X-Google-Smtp-Source: ABdhPJzDmDLe4ZPw7iSJPXc33ot7QHjnB/KrkLLd5Ij4DEcK/c6DsPaOS9aOJK39H4e6EnR2yDtA16zvqnpOF8cRLY0/9Q== 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:4112:: with SMTP id o18mr17366423yba.222.1613784801141; Fri, 19 Feb 2021 17:33:21 -0800 (PST) Date: Sat, 20 Feb 2021 01:32:55 +0000 In-Reply-To: <20210220013255.1083202-1-matthewgarrett@google.com> Message-Id: <20210220013255.1083202-10-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 9/9] pm: hibernate: seal the encryption key with a PCR policy 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 Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org The key blob is not secret, and by default the TPM will happily unseal it regardless of system state. We can protect against that by sealing the secret with a PCR policy - if the current PCR state doesn't match, the TPM will refuse to release the secret. For now let's just seal it to PCR 23. In the long term we may want a more flexible policy around this, such as including PCR 7. Signed-off-by: Matthew Garrett --- include/linux/tpm.h | 4 ++ kernel/power/tpm.c | 161 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 160 insertions(+), 5 deletions(-) diff --git a/include/linux/tpm.h b/include/linux/tpm.h index f6970986b097..2e0141978c87 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -225,18 +225,22 @@ enum tpm2_command_codes { TPM2_CC_CONTEXT_LOAD = 0x0161, TPM2_CC_CONTEXT_SAVE = 0x0162, TPM2_CC_FLUSH_CONTEXT = 0x0165, + TPM2_CC_START_AUTH_SESSION = 0x0176, TPM2_CC_VERIFY_SIGNATURE = 0x0177, TPM2_CC_GET_CAPABILITY = 0x017A, TPM2_CC_GET_RANDOM = 0x017B, TPM2_CC_PCR_READ = 0x017E, + TPM2_CC_POLICY_PCR = 0x017F, TPM2_CC_PCR_EXTEND = 0x0182, TPM2_CC_EVENT_SEQUENCE_COMPLETE = 0x0185, TPM2_CC_HASH_SEQUENCE_START = 0x0186, + TPM2_CC_POLICY_GET_DIGEST = 0x0189, TPM2_CC_CREATE_LOADED = 0x0191, TPM2_CC_LAST = 0x0193, /* Spec 1.36 */ }; enum tpm2_permanent_handles { + TPM2_RH_NULL = 0x40000007, TPM2_RS_PW = 0x40000009, }; diff --git a/kernel/power/tpm.c b/kernel/power/tpm.c index 34e6cfb98ce4..5de27c2f08be 100644 --- a/kernel/power/tpm.c +++ b/kernel/power/tpm.c @@ -125,6 +125,118 @@ static int swsusp_enc_dec(struct trusted_key_payload *payload, char *buf, return ret; } +static int tpm_setup_policy(struct tpm_chip *chip, int *session_handle) +{ + struct tpm_header *head; + struct tpm_buf buf; + char nonce[32] = {0x00}; + int rc; + + rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, + TPM2_CC_START_AUTH_SESSION); + if (rc) + return rc; + + /* Decrypt key */ + tpm_buf_append_u32(&buf, TPM2_RH_NULL); + + /* Auth entity */ + tpm_buf_append_u32(&buf, TPM2_RH_NULL); + + /* Nonce - blank is fine here */ + tpm_buf_append_u16(&buf, sizeof(nonce)); + tpm_buf_append(&buf, nonce, sizeof(nonce)); + + /* Encrypted secret - empty */ + tpm_buf_append_u16(&buf, 0); + + /* Policy type - session */ + tpm_buf_append_u8(&buf, 0x01); + + /* Encryption type - NULL */ + tpm_buf_append_u16(&buf, TPM_ALG_NULL); + + /* Hash type - SHA256 */ + tpm_buf_append_u16(&buf, TPM_ALG_SHA256); + + rc = tpm_send(chip, buf.data, tpm_buf_length(&buf)); + + if (rc) + goto out; + + head = (struct tpm_header *)buf.data; + + if (be32_to_cpu(head->length) != sizeof(struct tpm_header) + + sizeof(int) + sizeof(u16) + sizeof(nonce)) { + rc = -EINVAL; + goto out; + } + + *session_handle = be32_to_cpu(*(int *)&buf.data[10]); + memcpy(nonce, &buf.data[16], sizeof(nonce)); + + tpm_buf_destroy(&buf); + + rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_POLICY_PCR); + if (rc) + return rc; + + tpm_buf_append_u32(&buf, *session_handle); + + /* PCR digest - read from the PCR, we'll verify creation data later */ + tpm_buf_append_u16(&buf, 0); + + /* One PCR */ + tpm_buf_append_u32(&buf, 1); + + /* SHA256 banks */ + tpm_buf_append_u16(&buf, TPM_ALG_SHA256); + + /* Select PCR 23 */ + tpm_buf_append_u32(&buf, 0x03000080); + + rc = tpm_send(chip, buf.data, tpm_buf_length(&buf)); + + if (rc) + goto out; + +out: + tpm_buf_destroy(&buf); + return rc; +} + +static int tpm_policy_get_digest(struct tpm_chip *chip, int handle, + char *digest) +{ + struct tpm_header *head; + struct tpm_buf buf; + int rc; + + rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_POLICY_GET_DIGEST); + if (rc) + return rc; + + tpm_buf_append_u32(&buf, handle); + + rc = tpm_send(chip, buf.data, tpm_buf_length(&buf)); + + if (rc) + goto out; + + head = (struct tpm_header *)buf.data; + if (be32_to_cpu(head->length) != sizeof(struct tpm_header) + + sizeof(u16) + SHA256_DIGEST_SIZE) { + rc = -EINVAL; + goto out; + } + + memcpy(digest, &buf.data[12], SHA256_DIGEST_SIZE); +out: + tpm_buf_destroy(&buf); + + return rc; +} + static int tpm_certify_creationdata(struct tpm_chip *chip, struct trusted_key_payload *payload) { @@ -182,11 +294,14 @@ int swsusp_encrypt_digest(struct swsusp_header *header) const struct cred *cred = current_cred(); struct trusted_key_payload *payload; struct tpm_digest *digests = NULL; + char policy[SHA256_DIGEST_SIZE]; + char *policydigest = NULL; struct tpm_chip *chip; struct key *key; + int session_handle; int ret, i; - - char *keyinfo = "new\t32\tkeyhandle=0x81000001\tcreationpcrs=0x00800000"; + char *keyinfo = NULL; + char *keytemplate = "new\t32\tkeyhandle=0x81000001\tcreationpcrs=0x00800000\tpolicydigest=%s"; chip = tpm_default_chip(); @@ -213,10 +328,35 @@ int swsusp_encrypt_digest(struct swsusp_header *header) memcpy(&digests[i], &digest, sizeof(digest)); } + policydigest = kmalloc(SHA256_DIGEST_SIZE * 2 + 1, GFP_KERNEL); + if (!policydigest) { + ret = -ENOMEM; + goto reset; + } + ret = tpm_pcr_extend(chip, 23, digests); if (ret != 0) goto reset; + ret = tpm_setup_policy(chip, &session_handle); + + if (ret != 0) + goto reset; + + ret = tpm_policy_get_digest(chip, session_handle, policy); + + if (ret != 0) + goto reset; + + bin2hex(policydigest, policy, SHA256_DIGEST_SIZE); + policydigest[64] = '\0'; + + keyinfo = kasprintf(GFP_KERNEL, keytemplate, policydigest); + 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); @@ -228,6 +368,7 @@ int swsusp_encrypt_digest(struct swsusp_header *header) ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL, NULL); + if (ret < 0) goto error; @@ -244,6 +385,8 @@ int swsusp_encrypt_digest(struct swsusp_header *header) key_revoke(key); key_put(key); reset: + kfree(keyinfo); + kfree(policydigest); kfree(digests); tpm_pcr_reset(chip, 23); return ret; @@ -252,13 +395,14 @@ int swsusp_encrypt_digest(struct swsusp_header *header) int swsusp_decrypt_digest(struct swsusp_header *header) { const struct cred *cred = current_cred(); - char *keytemplate = "load\t%s\tkeyhandle=0x81000001"; + char *keytemplate = "load\t%s\tkeyhandle=0x81000001\tpolicyhandle=0x%x"; struct trusted_key_payload *payload; struct tpm_digest *digests = NULL; char certhash[SHA256_DIGEST_SIZE]; char *blobstring = NULL; char *keyinfo = NULL; struct tpm_chip *chip; + int session_handle; struct key *key; int i, ret; @@ -291,15 +435,22 @@ int swsusp_decrypt_digest(struct swsusp_header *header) if (ret != 0) goto reset; - blobstring = kmalloc(header->blob_len * 2, GFP_KERNEL); + ret = tpm_setup_policy(chip, &session_handle); + + if (ret != 0) + goto reset; + + blobstring = kmalloc(header->blob_len * 2 + 1, GFP_KERNEL); if (!blobstring) { ret = -ENOMEM; goto reset; } bin2hex(blobstring, header->blob, header->blob_len); + blobstring[header->blob_len * 2] = '\0'; - keyinfo = kasprintf(GFP_KERNEL, keytemplate, blobstring); + keyinfo = kasprintf(GFP_KERNEL, keytemplate, blobstring, + session_handle); if (!keyinfo) { ret = -ENOMEM; goto reset;