blob: 22f7f8c08eb480600c9966df03788df15475456a [file] [log] [blame]
/*
* Licensed Materials - Property of IBM
*
* trousers - An open source TCG Software Stack
*
* (C) Copyright International Business Machines Corp. 2004-2006
*
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "trousers/tss.h"
#include "trousers_types.h"
#include "tcs_tsp.h"
#include "tcs_utils.h"
#include "tcs_int_literals.h"
#include "capabilities.h"
#include "tcslog.h"
#include "tcsd_wrap.h"
#include "tcsd.h"
#include "auth_mgr.h"
#include "req_mgr.h"
MUTEX_DECLARE_EXTERN(tcsp_lock);
/* Note: The after taking the auth_mgr_lock in any of the functions below, the
* mem_cache_lock cannot be taken without risking a deadlock. So, the auth_mgr
* functions must be "self-contained" wrt locking */
/* no locking done in init since its called by only a single thread */
TSS_RESULT
auth_mgr_init()
{
memset(&auth_mgr, 0, sizeof(struct _auth_mgr));
auth_mgr.max_auth_sessions = tpm_metrics.num_auths;
auth_mgr.overflow = calloc(TSS_DEFAULT_OVERFLOW_AUTHS, sizeof(COND_VAR *));
if (auth_mgr.overflow == NULL) {
LogError("malloc of %zd bytes failed",
(TSS_DEFAULT_OVERFLOW_AUTHS * sizeof(COND_VAR *)));
return TCSERR(TSS_E_OUTOFMEMORY);
}
auth_mgr.auth_mapper = calloc(TSS_DEFAULT_AUTH_TABLE_SIZE, sizeof(struct auth_map));
if (auth_mgr.auth_mapper == NULL) {
LogError("malloc of %zd bytes failed",
(TSS_DEFAULT_AUTH_TABLE_SIZE * sizeof(struct auth_map)));
return TCSERR(TSS_E_OUTOFMEMORY);
}
auth_mgr.auth_mapper_size = TSS_DEFAULT_AUTH_TABLE_SIZE;
return TSS_SUCCESS;
}
TSS_RESULT
auth_mgr_final()
{
int i;
/* wake up any sleeping threads, so they can be joined */
for (i = 0; i < TSS_DEFAULT_OVERFLOW_AUTHS; i++) {
if (auth_mgr.overflow[i] != NULL)
COND_SIGNAL(auth_mgr.overflow[i]);
}
free(auth_mgr.overflow);
free(auth_mgr.auth_mapper);
return TSS_SUCCESS;
}
TSS_RESULT
auth_mgr_save_ctx(TCS_CONTEXT_HANDLE hContext)
{
TSS_RESULT result;
UINT32 i;
for (i = 0; i < auth_mgr.auth_mapper_size; i++) {
if (auth_mgr.auth_mapper[i].full == TRUE &&
auth_mgr.auth_mapper[i].swap == NULL &&
auth_mgr.auth_mapper[i].tcs_ctx != hContext) {
LogDebug("Calling TPM_SaveAuthContext for TCS CTX %x. Swapping out: TCS %x "
"TPM %x", hContext, auth_mgr.auth_mapper[i].tcs_ctx,
auth_mgr.auth_mapper[i].tpm_handle);
if ((result = TPM_SaveAuthContext(auth_mgr.auth_mapper[i].tpm_handle,
&auth_mgr.auth_mapper[i].swap_size,
&auth_mgr.auth_mapper[i].swap))) {
LogDebug("TPM_SaveAuthContext failed: 0x%x", result);
return result;
}
}
}
return TSS_SUCCESS;
}
/* if there's a TCS context waiting to get auth, wake it up or swap it in */
void
auth_mgr_swap_in()
{
if (auth_mgr.overflow[auth_mgr.of_tail] != NULL) {
LogDebug("waking up thread %zd, auth slot has opened", THREAD_ID);
/* wake up the next sleeping thread in order and increment tail */
COND_SIGNAL(auth_mgr.overflow[auth_mgr.of_tail]);
auth_mgr.overflow[auth_mgr.of_tail] = NULL;
auth_mgr.of_tail = (auth_mgr.of_tail + 1) % TSS_DEFAULT_OVERFLOW_AUTHS;
} else {
/* else nobody needs to be swapped in, so continue */
LogDebug("no threads need to be signaled.");
}
}
/* we need to swap out an auth context or add a waiting context to the overflow queue */
TSS_RESULT
auth_mgr_swap_out(TCS_CONTEXT_HANDLE hContext)
{
COND_VAR *cond;
/* If the TPM can do swapping and it succeeds, return, else cond wait below */
if (tpm_metrics.authctx_swap && !auth_mgr_save_ctx(hContext))
return TSS_SUCCESS;
if ((cond = ctx_get_cond_var(hContext)) == NULL) {
LogError("Auth swap variable not found for TCS context 0x%x", hContext);
return TCSERR(TSS_E_INTERNAL_ERROR);
}
/* Test whether we are the last awake thread. If we are, we can't go to sleep
* since then there'd be no worker thread to wake the others up. This situation
* can arise when we're on a busy system who's TPM doesn't support auth ctx
* swapping.
*/
if (auth_mgr.sleeping_threads == (tcsd_options.num_threads - 1)) {
LogError("auth mgr failing: too many threads already waiting");
return TCPA_E_RESOURCES;
}
if (auth_mgr.overflow[auth_mgr.of_head] == NULL) {
auth_mgr.overflow[auth_mgr.of_head] = cond;
auth_mgr.of_head = (auth_mgr.of_head + 1) % TSS_DEFAULT_OVERFLOW_AUTHS;
/* go to sleep */
LogDebug("thread %zd going to sleep until auth slot opens", THREAD_ID);
auth_mgr.sleeping_threads++;
COND_WAIT(cond, &tcsp_lock);
auth_mgr.sleeping_threads--;
} else {
LogError("auth mgr queue is full! There are currently %d "
"TCS sessions waiting on an auth session!",
TSS_DEFAULT_OVERFLOW_AUTHS);
return TCSERR(TSS_E_INTERNAL_ERROR);
}
return TSS_SUCCESS;
}
/* close all auth contexts associated with this TCS_CONTEXT_HANDLE */
TSS_RESULT
auth_mgr_close_context(TCS_CONTEXT_HANDLE tcs_handle)
{
UINT32 i;
TSS_RESULT result;
for (i = 0; i < auth_mgr.auth_mapper_size; i++) {
if (auth_mgr.auth_mapper[i].full == TRUE &&
auth_mgr.auth_mapper[i].tcs_ctx == tcs_handle) {
result = internal_TerminateHandle(auth_mgr.auth_mapper[i].tpm_handle);
if (result == TCPA_E_INVALID_AUTHHANDLE) {
LogDebug("Tried to close an invalid auth handle: %x",
auth_mgr.auth_mapper[i].tpm_handle);
} else if (result != TCPA_SUCCESS) {
LogDebug("TPM_TerminateHandle returned %d", result);
}
auth_mgr.open_auth_sessions--;
auth_mgr.auth_mapper[i].full = FALSE;
LogDebug("released auth for TCS %x TPM %x", tcs_handle,
auth_mgr.auth_mapper[i].tpm_handle);
auth_mgr_swap_in();
}
}
return TSS_SUCCESS;
}
void
auth_mgr_release_auth(TPM_AUTH *auth0, TPM_AUTH *auth1, TCS_CONTEXT_HANDLE tcs_handle)
{
if (auth0)
auth_mgr_release_auth_handle(auth0->AuthHandle, tcs_handle,
auth0->fContinueAuthSession);
if (auth1)
auth_mgr_release_auth_handle(auth1->AuthHandle, tcs_handle,
auth1->fContinueAuthSession);
}
/* unload the auth ctx associated with this auth handle */
TSS_RESULT
auth_mgr_release_auth_handle(TCS_AUTHHANDLE tpm_auth_handle, TCS_CONTEXT_HANDLE tcs_handle,
TSS_BOOL cont)
{
UINT32 i;
TSS_RESULT result = TSS_SUCCESS;
for (i = 0; i < auth_mgr.auth_mapper_size; i++) {
if (auth_mgr.auth_mapper[i].full == TRUE &&
auth_mgr.auth_mapper[i].tpm_handle == tpm_auth_handle &&
auth_mgr.auth_mapper[i].tcs_ctx == tcs_handle) {
if (cont) {
/* Only termininate when still in use */
result = internal_TerminateHandle(
auth_mgr.auth_mapper[i].tpm_handle);
if (result == TCPA_E_INVALID_AUTHHANDLE) {
LogDebug("Tried to close an invalid auth handle: %x",
auth_mgr.auth_mapper[i].tpm_handle);
} else if (result != TCPA_SUCCESS) {
LogDebug("TPM_TerminateHandle returned %d", result);
}
}
auth_mgr.open_auth_sessions--;
auth_mgr.auth_mapper[i].full = FALSE;
LogDebug("released auth for TCS %x TPM %x",
auth_mgr.auth_mapper[i].tcs_ctx, tpm_auth_handle);
auth_mgr_swap_in();
}
}
return result;
}
TSS_RESULT
auth_mgr_check(TCS_CONTEXT_HANDLE tcsContext, TPM_AUTHHANDLE *tpm_auth_handle)
{
UINT32 i;
TSS_RESULT result = TCSERR(TSS_E_INTERNAL_ERROR);
for (i = 0; i < auth_mgr.auth_mapper_size; i++) {
if (auth_mgr.auth_mapper[i].full == TRUE &&
auth_mgr.auth_mapper[i].tpm_handle == *tpm_auth_handle &&
auth_mgr.auth_mapper[i].tcs_ctx == tcsContext) {
result = TSS_SUCCESS;
/* We have a record of this session, now swap it into the TPM if need be. */
if (auth_mgr.auth_mapper[i].swap) {
LogDebug("TPM_LoadAuthContext for TCS %x TPM %x", tcsContext,
auth_mgr.auth_mapper[i].tpm_handle);
result = TPM_LoadAuthContext(auth_mgr.auth_mapper[i].swap_size,
auth_mgr.auth_mapper[i].swap,
tpm_auth_handle);
free(auth_mgr.auth_mapper[i].swap);
auth_mgr.auth_mapper[i].swap = NULL;
auth_mgr.auth_mapper[i].swap_size = 0;
if (result == TSS_SUCCESS) {
LogDebug("TPM_LoadAuthContext succeeded. Old TPM: %x, New "
"TPM: %x", auth_mgr.auth_mapper[i].tpm_handle,
*tpm_auth_handle);
auth_mgr.auth_mapper[i].tpm_handle = *tpm_auth_handle;
} else {
LogDebug("TPM_LoadAuthContext failed: 0x%x.", result);
}
}
break;
}
}
if (result == TCSERR(TSS_E_INTERNAL_ERROR)) {
LogDebug("no auth in table for TCS handle 0x%x", tcsContext);
}
return result;
}
TSS_RESULT
auth_mgr_add(TCS_CONTEXT_HANDLE tcsContext, TCS_AUTHHANDLE tpm_auth_handle)
{
TSS_RESULT result = TCSERR(TSS_E_INTERNAL_ERROR);
UINT32 i;
for (i = 0; i < auth_mgr.auth_mapper_size; i++) {
if (auth_mgr.auth_mapper[i].full == FALSE) {
auth_mgr.auth_mapper[i].tpm_handle = tpm_auth_handle;
auth_mgr.auth_mapper[i].tcs_ctx = tcsContext;
auth_mgr.auth_mapper[i].full = TRUE;
result = TSS_SUCCESS;
auth_mgr.open_auth_sessions++;
LogDebug("added auth for TCS %x TPM %x", tcsContext, tpm_auth_handle);
break;
}
}
if (result == TCSERR(TSS_E_INTERNAL_ERROR)) {
struct auth_map *tmp = auth_mgr.auth_mapper;
LogDebugFn("Thread %zd growing the auth table to %u entries", THREAD_ID,
auth_mgr.auth_mapper_size + TSS_DEFAULT_AUTH_TABLE_SIZE);
auth_mgr.auth_mapper = calloc(auth_mgr.auth_mapper_size +
TSS_DEFAULT_AUTH_TABLE_SIZE, sizeof(struct auth_map));
if (auth_mgr.auth_mapper == NULL) {
auth_mgr.auth_mapper = tmp;
result = TCSERR(TSS_E_OUTOFMEMORY);
} else {
memcpy(auth_mgr.auth_mapper, tmp,
auth_mgr.auth_mapper_size * sizeof(struct auth_map));
auth_mgr.auth_mapper_size += TSS_DEFAULT_AUTH_TABLE_SIZE;
result = TSS_SUCCESS;
LogDebugFn("Success.");
}
}
return result;
}
/* A thread wants a new OIAP or OSAP session with the TPM. Returning TRUE indicates that we should
* allow it to open the session, FALSE to indicate that the request should be queued or have
* another thread's session swapped out to make room for it.
*/
TSS_BOOL
auth_mgr_req_new(TCS_CONTEXT_HANDLE hContext)
{
UINT32 i, opened = 0;
for (i = 0; i < auth_mgr.auth_mapper_size; i++) {
if (auth_mgr.auth_mapper[i].full == TRUE &&
auth_mgr.auth_mapper[i].tcs_ctx == hContext) {
opened++;
}
}
/* If this TSP has already opened its max open auth handles, deny another open */
if (opened >= MAX(2, (UINT32)auth_mgr.max_auth_sessions/2)) {
LogDebug("Max opened auth handles already opened.");
return FALSE;
}
/* if we have one opened already and there's a slot available, ok */
if (opened && ((auth_mgr.max_auth_sessions - auth_mgr.open_auth_sessions) >= 1))
return TRUE;
/* we don't already have one open and there are at least 2 slots left */
if ((auth_mgr.max_auth_sessions - auth_mgr.open_auth_sessions) >= 2)
return TRUE;
LogDebug("Request for new auth handle denied by TCS. (%d opened sessions)", opened);
return FALSE;
}
TSS_RESULT
auth_mgr_oiap(TCS_CONTEXT_HANDLE hContext, /* in */
TCS_AUTHHANDLE *authHandle, /* out */
TCPA_NONCE *nonce0) /* out */
{
TSS_RESULT result;
/* are the maximum number of auth sessions open? */
if (auth_mgr_req_new(hContext) == FALSE) {
if ((result = auth_mgr_swap_out(hContext)))
goto done;
}
if ((result = TCSP_OIAP_Internal(hContext, authHandle, nonce0)))
goto done;
/* success, add an entry to the table */
result = auth_mgr_add(hContext, *authHandle);
done:
return result;
}
TSS_RESULT
auth_mgr_osap(TCS_CONTEXT_HANDLE hContext, /* in */
TCPA_ENTITY_TYPE entityType, /* in */
UINT32 entityValue, /* in */
TCPA_NONCE nonceOddOSAP, /* in */
TCS_AUTHHANDLE *authHandle, /* out */
TCPA_NONCE *nonceEven, /* out */
TCPA_NONCE *nonceEvenOSAP) /* out */
{
TSS_RESULT result;
UINT32 newEntValue = 0;
/* if ET is not KEYHANDLE or KEY, newEntValue is a don't care */
if (entityType == TCPA_ET_KEYHANDLE || entityType == TCPA_ET_KEY) {
if (ensureKeyIsLoaded(hContext, entityValue, &newEntValue))
return TCSERR(TSS_E_FAIL);
} else {
newEntValue = entityValue;
}
/* are the maximum number of auth sessions open? */
if (auth_mgr_req_new(hContext) == FALSE) {
if ((result = auth_mgr_swap_out(hContext)))
goto done;
}
if ((result = TCSP_OSAP_Internal(hContext, entityType, newEntValue, nonceOddOSAP,
authHandle, nonceEven, nonceEvenOSAP)))
goto done;
/* success, add an entry to the table */
result = auth_mgr_add(hContext, *authHandle);
done:
return result;
}
/* This function is only called directly from the TSP, we use other internal routines to free
* our handles. */
TSS_RESULT
TCSP_TerminateHandle_Internal(TCS_CONTEXT_HANDLE hContext, /* in */
TCS_AUTHHANDLE handle) /* in */
{
TSS_RESULT result;
LogDebug("Entering TCSI_TerminateHandle");
if ((result = ctx_verify_context(hContext)))
return result;
if ((result = auth_mgr_check(hContext, &handle)))
return result;
result = auth_mgr_release_auth_handle(handle, hContext, TRUE);
LogResult("Terminate Handle", result);
return result;
}
TSS_RESULT
TPM_SaveAuthContext(TPM_AUTHHANDLE handle, UINT32 *size, BYTE **blob)
{
UINT64 offset;
UINT32 trash, bsize;
TSS_RESULT result;
BYTE txBlob[TSS_TPM_TXBLOB_SIZE];
offset = 10;
LoadBlob_UINT32(&offset, handle, txBlob);
LoadBlob_Header(TPM_TAG_RQU_COMMAND, offset, TPM_ORD_SaveAuthContext, txBlob);
if ((result = req_mgr_submit_req(txBlob)))
return result;
result = UnloadBlob_Header(txBlob, &trash);
LogDebug("total packet size received from TPM: %u", trash);
if (!result) {
offset = 10;
UnloadBlob_UINT32(&offset, &bsize, txBlob);
LogDebug("Reported blob size from TPM: %u", bsize);
*blob = malloc(bsize);
if (*blob == NULL) {
LogError("malloc of %u bytes failed.", bsize);
return TCSERR(TSS_E_OUTOFMEMORY);
}
UnloadBlob(&offset, bsize, txBlob, *blob);
*size = bsize;
}
return result;
}
TSS_RESULT
TPM_LoadAuthContext(UINT32 size, BYTE *blob, TPM_AUTHHANDLE *handle)
{
UINT64 offset;
UINT32 trash;
TSS_RESULT result;
BYTE txBlob[TSS_TPM_TXBLOB_SIZE];
LogDebugFn("Loading %u byte auth blob back into TPM", size);
offset = 10;
LoadBlob_UINT32(&offset, size, txBlob);
LoadBlob(&offset, size, txBlob, blob);
LoadBlob_Header(TPM_TAG_RQU_COMMAND, offset, TPM_ORD_LoadAuthContext, txBlob);
if ((result = req_mgr_submit_req(txBlob)))
return result;
result = UnloadBlob_Header(txBlob, &trash);
if (!result) {
offset = 10;
UnloadBlob_UINT32(&offset, handle, txBlob);
}
return result;
}