From patchwork Wed Jan 18 20:48:09 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Bottomley X-Patchwork-Id: 9524765 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 2EF9C601B7 for ; Wed, 18 Jan 2017 20:48:56 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1C850285AB for ; Wed, 18 Jan 2017 20:48:56 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 0F96928637; Wed, 18 Jan 2017 20:48:56 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 0527F285AB for ; Wed, 18 Jan 2017 20:48:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751080AbdARUsu (ORCPT ); Wed, 18 Jan 2017 15:48:50 -0500 Received: from bedivere.hansenpartnership.com ([66.63.167.143]:46530 "EHLO bedivere.hansenpartnership.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751859AbdARUsa (ORCPT ); Wed, 18 Jan 2017 15:48:30 -0500 Received: from localhost (localhost [127.0.0.1]) by bedivere.hansenpartnership.com (Postfix) with ESMTP id 5F1898EE216; Wed, 18 Jan 2017 12:48:12 -0800 (PST) Received: from bedivere.hansenpartnership.com ([127.0.0.1]) by localhost (bedivere.hansenpartnership.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id ghZsi_-NT03p; Wed, 18 Jan 2017 12:48:12 -0800 (PST) Received: from [9.232.160.153] (unknown [129.33.253.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by bedivere.hansenpartnership.com (Postfix) with ESMTPSA id B98458EE07D; Wed, 18 Jan 2017 12:48:11 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=hansenpartnership.com; s=20151216; t=1484772492; bh=2vrER2HE5KP+ADMNW0ex/BDwCRFvYgoDEnuVXdwtYWU=; h=Subject:From:To:Cc:Date:From; b=goqeWidjHsWy7c+OHWwjoMAaf6QUUJ1ehZG5+XupYrEsPxeeev0YEA6EaZwn5YoDO madgvXxqfbLA+TORZl7FeeGan6uwsL5b9kqOHR3cbUpH95ROqBb8/faPp+U+MSTKQb CpLWZoE/RxO4PSzYZvKjowJbUAAVkNHqzjrKalvo= Message-ID: <1484772489.2396.2.camel@HansenPartnership.com> Subject: [RFC] tpm2-space: add handling for global session exhaustion From: James Bottomley To: tpmdd-devel@lists.sourceforge.net Cc: linux-security-module@vger.kernel.org, open list Date: Wed, 18 Jan 2017 15:48:09 -0500 X-Mailer: Evolution 3.16.5 Mime-Version: 1.0 Sender: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: X-Virus-Scanned: ClamAV using ClamSMTP In a TPM2, sessions can be globally exhausted once there are TPM_PT_ACTIVE_SESSION_MAX of them (even if they're all context saved). The Strategy for handling this is to keep a global count of all the sessions along with their creation time. Then if we see the TPM run out of sessions (via the TPM_RC_SESSION_HANDLES) we first wait for one to become free, but if it doesn't, we forcibly evict an existing one. The eviction strategy waits until the current command is repeated to evict the session which should guarantee there is an available slot. On the force eviction case, we make sure that the victim session is at least SESSION_TIMEOUT old (currently 2 seconds). The wait queue for session slots is a FIFO one, ensuring that once we run out of sessions, everyone will get a session in a bounded time and once they get one, they'll have SESSION_TIMEOUT to use it before it may be subject to eviction. Signed-off-by: James Bottomley --- To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c index a625884..c959b09 100644 --- a/drivers/char/tpm/tpm-chip.c +++ b/drivers/char/tpm/tpm-chip.c @@ -164,6 +164,7 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev, mutex_init(&chip->tpm_mutex); init_rwsem(&chip->ops_sem); + init_waitqueue_head(&chip->session_wait); chip->ops = ops; diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index 9923daa..38cc21c 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -95,6 +95,7 @@ enum tpm2_return_codes { TPM2_RC_HANDLE = 0x008B, TPM2_RC_INITIALIZE = 0x0100, /* RC_VER1 */ TPM2_RC_DISABLED = 0x0120, + TPM2_RC_SESSION_HANDLES = 0x0905, TPM2_RC_TESTING = 0x090A, /* RC_WARN */ }; @@ -136,7 +137,8 @@ enum tpm2_capabilities { }; enum tpm2_properties { - TPM_PT_TOTAL_COMMANDS = 0x0129, + TPM_PT_TOTAL_COMMANDS = 0x0129, + TPM_PT_ACTIVE_SESSIONS_MAX = 0x0111, }; enum tpm2_startup_types { @@ -160,8 +162,24 @@ struct tpm_space { u8 *context_buf; u32 session_tbl[6]; u8 *session_buf; + u32 reserved_handle; }; +#define TPM2_HANDLE_FORCE_EVICT 0xFFFFFFFF + +static inline void tpm2_session_force_evict(struct tpm_space *space) +{ + /* if reserved handle is not empty, we already have a + * session for eviction, so no need to force one + */ + if (space->reserved_handle == 0) + space->reserved_handle = TPM2_HANDLE_FORCE_EVICT; +} +static inline bool tpm2_is_session_force_evict(struct tpm_space *space) +{ + return space->reserved_handle == TPM2_HANDLE_FORCE_EVICT; +} + enum tpm_chip_flags { TPM_CHIP_FLAG_TPM2 = BIT(1), TPM_CHIP_FLAG_IRQ = BIT(2), @@ -174,6 +192,12 @@ struct tpm_chip_seqops { const struct seq_operations *seqops; }; +struct tpm_sessions { + struct tpm_space *space; + u32 handle; + unsigned long created; +}; + struct tpm_chip { struct device dev, devrm; struct cdev cdev, cdevrm; @@ -214,8 +238,12 @@ struct tpm_chip { #endif /* CONFIG_ACPI */ struct tpm_space work_space; + struct tpm_space *space; u32 nr_commands; u32 *cc_attrs_tbl; + struct tpm_sessions *sessions; + int max_sessions; + wait_queue_head_t session_wait; }; #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev) @@ -568,6 +596,13 @@ int tpm2_pcr_extend(struct tpm_chip *chip, int pcr_idx, const u8 *hash); int tpm2_get_random(struct tpm_chip *chip, u8 *out, size_t max); void tpm2_flush_context_cmd(struct tpm_chip *chip, u32 handle, unsigned int flags); +static inline void tpm2_session_clear_reserved(struct tpm_chip *chip, + struct tpm_space *space) +{ + if (space->reserved_handle && !tpm2_is_session_force_evict(space)) + tpm2_flush_context_cmd(chip, space->reserved_handle, 0); + space->reserved_handle = 0; +} int tpm2_seal_trusted(struct tpm_chip *chip, struct trusted_key_payload *payload, struct trusted_key_options *options); diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index e1c1bbd..ac5c0a2 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -1007,6 +1007,7 @@ int tpm2_auto_startup(struct tpm_chip *chip) { struct tpm_buf buf; u32 nr_commands; + u32 nr_sessions; int rc; int i; @@ -1067,6 +1068,20 @@ int tpm2_auto_startup(struct tpm_chip *chip) chip->nr_commands = nr_commands; tpm_buf_destroy(&buf); + rc = tpm2_get_tpm_pt(chip, TPM_PT_ACTIVE_SESSIONS_MAX, + &nr_sessions, NULL); + if (rc) + goto out; + + if (nr_sessions > 256) + nr_sessions = 256; + + chip->max_sessions = nr_sessions; + chip->sessions = devm_kzalloc(&chip->dev, + nr_sessions * sizeof(*chip->sessions), + GFP_KERNEL); + if (!chip->sessions) + rc = -ENOMEM; out: if (rc > 0) rc = -ENODEV; diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c index 04c9431..42c8c84 100644 --- a/drivers/char/tpm/tpm2-space.c +++ b/drivers/char/tpm/tpm2-space.c @@ -34,6 +34,169 @@ struct tpm2_context { __be16 blob_size; } __packed; +static struct tpm_sessions *tpm2_session_chip_get(struct tpm_chip *chip) +{ + int i; + + for (i = 0; i < chip->max_sessions; i++) + if (chip->sessions[i].space == NULL) + return &chip->sessions[i]; + + return NULL; +} + +static struct tpm_sessions *tpm2_session_chip_find_oldest(struct tpm_chip *chip) +{ + struct tpm_sessions *sess = NULL; + int i; + + for (i = 0; i < chip->max_sessions; i++) { + if (chip->sessions[i].space == NULL) + continue; + + if (!sess || time_after(sess->created, + chip->sessions[i].created)) + sess = &chip->sessions[i]; + } + + return sess; +} + +static void tpm2_session_chip_add(struct tpm_chip *chip, + struct tpm_space *space, u32 h) +{ + struct tpm_sessions *sess = tpm2_session_chip_get(chip); + + sess->space = space; + sess->handle = h; + sess->created = jiffies; + dev_info(&chip->dev, "Added Session at %ld, handle %08x", sess - chip->sessions, h); +} + +static void tpm2_session_chip_remove(struct tpm_chip *chip, u32 h) +{ + int i; + + for (i = 0; i < chip->max_sessions; i++) + if (chip->sessions[i].handle == h) + break; + if (i == chip->max_sessions) { + dev_warn(&chip->dev, "Missing session %08x", h); + return; + } + + memset(&chip->sessions[i], 0, sizeof(chip->sessions[i])); + dev_info(&chip->dev, "Removed session at %d\n", i); + wake_up(&chip->session_wait); +} + +static int tpm2_session_forget(struct tpm_chip *chip, struct tpm_space *space, + u32 handle) +{ + int i, j; + struct tpm2_context *ctx; + + for (i = 0, j = 0; i < ARRAY_SIZE(space->session_tbl); i++) { + if (space->session_tbl[i] == 0) + continue; + + ctx = (struct tpm2_context *)&space->session_buf[j]; + j += sizeof(*ctx) + get_unaligned_be16(&ctx->blob_size); + + if (space->session_tbl[i] != handle) + continue; + + /* forget the session context */ + memcpy(ctx, &space->session_buf[j], PAGE_SIZE - j); + tpm2_session_chip_remove(chip, handle); + space->session_tbl[i] = 0; + break; + } + if (i == ARRAY_SIZE(space->session_tbl)) + return -EINVAL; + return 0; +} + +static int tpm2_session_wait(struct tpm_chip *chip, struct tpm_space *space) +{ + int rc, failed; + struct tpm_sessions *sess; + const unsigned long min_timeout = msecs_to_jiffies(2000); + unsigned long timeout = min_timeout; + DEFINE_WAIT(wait); + + for (failed = 0; ; ) { + prepare_to_wait(&chip->session_wait, &wait, TASK_INTERRUPTIBLE); + + mutex_unlock(&chip->tpm_mutex); + rc = schedule_timeout_interruptible(timeout); + mutex_lock(&chip->tpm_mutex); + + finish_wait(&chip->session_wait, &wait); + + if (signal_pending(current)) + /* got interrupted */ + return -EINTR; + + if (rc > 0 && !tpm2_is_session_force_evict(space)) + /* got woken, so slot is free. We don't + * reserve the slot here because a) we can't + * (no pending session in the TPM to evict) + * and b) no-one is hogging sessions, so no + * evidence of need. + */ + return 0; + + /* timed out or victim required; select a victim + * session to kill + */ + sess = tpm2_session_chip_find_oldest(chip); + if (sess == NULL) { + /* we get here when we can't create a session + * but there are no listed active sessions + * meaning they're all in various space + * structures as victim sessions. The wait + * queue is a fair sequence, so we need to + * wait a bit harder + */ + if (failed++ > 3) + break; + timeout *= 2; + dev_info(&chip->dev, "failed to get session, waiting for %us\n", jiffies_to_msecs(timeout)/1000); + continue; + } + /* is the victim old enough? */ + timeout = jiffies - sess->created; + if (timeout > min_timeout) + break; + /* otherwise wait until the victim is old enough */ + timeout = min_timeout - timeout; + } + if (sess == NULL) + /* still can't get a victim, give up */ + return -EINVAL; + + /* store the physical handle */ + space->reserved_handle = sess->handle; + dev_info(&chip->dev, "Selecting handle %08x for eviction\n", + space->reserved_handle); + + /* cause a mapping failure if this session handle is + * ever used in the victim space again + */ + tpm2_session_forget(chip, sess->space, sess->handle); + /* clear the session, but don't wake any other waiters */ + memset(sess, 0, sizeof(*sess)); + /* so now we have a saved physical handle but this handle is + * still in the tpm. After this we repeat the command, but + * flush the handle once we obtain the tpm_mutex on the repeat + * so, in theory, we should have a free handle to + * re-execute + */ + + return 0; +} + static int tpm2_context_save(struct tpm_chip *chip, u8 *area, int *offset, u32 handle) { @@ -124,9 +287,9 @@ static int tpm2_session_find(struct tpm_space *space, u32 handle) return i; } -static int tpm2_session_add(struct tpm_chip *chip, - struct tpm_space *space, u32 handle) +static int tpm2_session_add(struct tpm_chip *chip, u32 handle) { + struct tpm_space *space = &chip->work_space; int i; for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) @@ -139,35 +302,11 @@ static int tpm2_session_add(struct tpm_chip *chip, } space->session_tbl[i] = handle; + tpm2_session_chip_add(chip, chip->space, handle); return 0; } -static int tpm2_session_forget(struct tpm_space *space, u32 handle) -{ - int i, j; - struct tpm2_context *ctx; - - for (i = 0, j = 0; i < ARRAY_SIZE(space->session_tbl); i++) { - if (space->session_tbl[i] == 0) - continue; - - ctx = (struct tpm2_context *)&space->session_buf[j]; - j += sizeof(*ctx) + get_unaligned_be16(&ctx->blob_size); - - if (space->session_tbl[i] != handle) - continue; - - /* forget the session context */ - memcpy(ctx, &space->session_buf[j], PAGE_SIZE - j); - space->session_tbl[i] = 0; - break; - } - if (i == ARRAY_SIZE(space->session_tbl)) - return -EINVAL; - return 0; -} - /* if a space is active, emulate some commands */ static int tpm2_intercept(struct tpm_chip *chip, u32 cc, u8 *buf, size_t bufsiz) { @@ -187,7 +326,7 @@ static int tpm2_intercept(struct tpm_chip *chip, u32 cc, u8 *buf, size_t bufsiz) /* let the TPM figure out and return the error */ return 0; - return tpm2_session_forget(space, handle); + return tpm2_session_forget(chip, space, handle); } void tpm2_flush_space(struct tpm_chip *chip, struct tpm_space *space) @@ -200,10 +339,19 @@ void tpm2_flush_space(struct tpm_chip *chip, struct tpm_space *space) TPM_TRANSMIT_UNLOCKED); for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) { + if (!space->session_tbl[i]) + continue; + space->session_tbl[i] &= ~TPM2_HT_TAG_FOR_FLUSH; - if (space->session_tbl[i]) - tpm2_flush_context_cmd(chip, space->session_tbl[i], - TPM_TRANSMIT_UNLOCKED); + tpm2_session_chip_remove(chip, space->session_tbl[i]); + tpm2_flush_context_cmd(chip, space->session_tbl[i], + TPM_TRANSMIT_UNLOCKED); + } + if (space->reserved_handle && !tpm2_is_session_force_evict(space)) { + tpm2_flush_context_cmd(chip, space->reserved_handle, + TPM_TRANSMIT_UNLOCKED); + space->reserved_handle = 0; + /* subtlety here: if force evict is set, we don't clear it */ } } @@ -264,11 +412,13 @@ static void tpm2_unmap_sessions(struct tpm_chip *chip, u32 rc) if ((space->session_tbl[i] & TPM2_HT_TAG_FOR_FLUSH) != TPM2_HT_TAG_FOR_FLUSH) continue; - if (rc == TPM2_RC_SUCCESS) + + /* for unsuccessful command, keep session */ + space->session_tbl[i] &= ~TPM2_HT_TAG_FOR_FLUSH; + if (rc == TPM2_RC_SUCCESS) { + tpm2_session_chip_remove(chip, space->session_tbl[i]); space->session_tbl[i] = 0; - else - /* for unsuccessful command, keep session */ - space->session_tbl[i] &= ~TPM2_HT_TAG_FOR_FLUSH; + } } } @@ -387,6 +537,7 @@ int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, sizeof(space->session_tbl)); memcpy(chip->work_space.context_buf, space->context_buf, PAGE_SIZE); memcpy(chip->work_space.session_buf, space->session_buf, PAGE_SIZE); + chip->space = space; rc = tpm2_intercept(chip, cc, buf, bufsiz); if (rc) @@ -400,16 +551,28 @@ int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, if (rc) return rc; + if (space->reserved_handle && !tpm2_is_session_force_evict(space)) { + /* this is a trick to allow a previous command which + * failed because it was out of handle space to + * succeed. The handle is still in the TPM, so now we + * flush it under the tpm_mutex which should ensure we + * can create a new one + */ + tpm2_flush_context_cmd(chip, space->reserved_handle, + TPM_TRANSMIT_UNLOCKED); + space->reserved_handle = 0; + } + return 0; } -static int tpm2_map_response(struct tpm_chip *chip, u32 cc, u8 *rsp, size_t len) +static int tpm2_map_response(struct tpm_chip *chip, u32 cc, u8 *rsp, size_t len, + u32 return_code) { struct tpm_space *space = &chip->work_space; u32 phandle, phandle_type; u32 vhandle; u32 attrs; - u32 return_code = get_unaligned_be32((__be32 *)&rsp[6]); u16 tag = get_unaligned_be16((__be16 *)rsp); int i; int rc; @@ -439,7 +602,7 @@ static int tpm2_map_response(struct tpm_chip *chip, u32 cc, u8 *rsp, size_t len) return 0; if (phandle_type != TPM2_HT_TRANSIENT) - return tpm2_session_add(chip, space, phandle); + return tpm2_session_add(chip, phandle); /* Garbage collect a dead context. */ for (i = 0; i < ARRAY_SIZE(space->context_tbl); i++) { @@ -521,11 +684,12 @@ int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc, u8 *buf, size_t bufsiz) { int rc; + u32 return_code = get_unaligned_be32((__be32 *)&buf[6]); if (!space) return 0; - rc = tpm2_map_response(chip, cc, buf, bufsiz); + rc = tpm2_map_response(chip, cc, buf, bufsiz, return_code); if (rc) return rc; @@ -539,6 +703,12 @@ int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space, sizeof(space->session_tbl)); memcpy(space->context_buf, chip->work_space.context_buf, PAGE_SIZE); memcpy(space->session_buf, chip->work_space.session_buf, PAGE_SIZE); + chip->space = NULL; + + if (return_code == TPM2_RC_SESSION_HANDLES) { + tpm2_session_wait(chip, space); + return -EAGAIN; + } return 0; } diff --git a/drivers/char/tpm/tpms-dev.c b/drivers/char/tpm/tpms-dev.c index 12b6e34..b13b000 100644 --- a/drivers/char/tpm/tpms-dev.c +++ b/drivers/char/tpm/tpms-dev.c @@ -56,8 +56,23 @@ ssize_t tpms_write(struct file *file, const char __user *buf, { struct file_priv *fpriv = file->private_data; struct tpms_priv *priv = container_of(fpriv, struct tpms_priv, priv); + int count = 0; + const int max_count = 3; /* number of retries */ + int rc; - return tpm_common_write(file, buf, size, off, &priv->space); + for (count = 0; count < max_count; count++) { + rc = tpm_common_write(file, buf, size, off, &priv->space); + if (rc != -EAGAIN) + break; + if (count == max_count - 2) + /* second to last go around, force an eviction if + * this go fails, so final go should succeed + */ + tpm2_session_force_evict(&priv->space); + } + tpm2_session_clear_reserved(fpriv->chip, &priv->space); + + return rc; } const struct file_operations tpm_rm_fops = {