/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * Stub implementations of utility functions which call their linux-specific * equivalents. */ #include #include "2sysincludes.h" #include "2common.h" #include "tlcl.h" #include "tlcl_internal.h" #include "utility.h" #include "vboot_api.h" #include #include #include #include #include #include #include #include #include #include #include #define TPM_DEVICE_PATH "/dev/tpm0" /* Retry failed open()s for 5 seconds in 10ms polling intervals. */ #define OPEN_RETRY_DELAY_NS (10 * 1000 * 1000) #define OPEN_RETRY_MAX_NUM 500 #define COMM_RETRY_MAX_NUM 3 /* TODO: these functions should pass errors back rather than returning void */ /* TODO: if the only callers to these are just wrappers, should just * remove the wrappers and call us directly. */ /* The file descriptor for the TPM device. */ static int tpm_fd = -1; /* If the library should exit during an OS-level TPM failure. */ static int exit_on_failure = 1; /* Similar to VbExError, only handle the non-exit case. */ static int DoError(int result, const char* format, ...) { va_list ap; va_start(ap, format); fprintf(stderr, "ERROR: "); vfprintf(stderr, format, ap); va_end(ap); if (exit_on_failure) exit(1); return result; } /* Print |n| bytes from array |a| to stderr, with newlines. */ __attribute__((unused)) static void DbgPrintBytes(const uint8_t* a, int n) { int i; fprintf(stderr, "DEBUG: "); for (i = 0; i < n; i++) { if (i && i % 16 == 0) fprintf(stderr, "\nDEBUG: "); fprintf(stderr, "%02x ", a[i]); } fprintf(stderr, "\n"); } /* Executes a command on the TPM. */ static uint32_t TpmExecute(const uint8_t *in, const uint32_t in_len, uint8_t *out, uint32_t *pout_len) { uint8_t response[TPM_MAX_COMMAND_SIZE]; if (in_len <= 0) { return DoError(TPM_E_INPUT_TOO_SMALL, "invalid command length %d for command 0x%x\n", in_len, in[9]); } else if (tpm_fd < 0) { return DoError(TPM_E_NO_DEVICE, "the TPM device was not opened. " \ "Forgot to call TlclLibInit?\n"); } else { int n; int retries = 0; int first_errno = 0; /* Write command. Retry in case of communication errors. */ for ( ; retries < COMM_RETRY_MAX_NUM; ++retries) { n = write(tpm_fd, in, in_len); if (n >= 0) { break; } if (retries == 0) { first_errno = errno; } VB2_DEBUG("TPM: write attempt %d failed: %s\n", retries + 1, strerror(errno)); } if (n < 0) { return DoError(TPM_E_WRITE_FAILURE, "write failure to TPM device: %s " "(first error %d)\n", strerror(errno), first_errno); } else if (n != in_len) { return DoError(TPM_E_WRITE_FAILURE, "bad write size to TPM device: %d vs %u " "(%d retries, first error %d)\n", n, in_len, retries, first_errno); } /* Read response. Retry in case of communication errors. */ for (retries = 0, first_errno = 0; retries < COMM_RETRY_MAX_NUM; ++retries) { n = read(tpm_fd, response, sizeof(response)); if (n >= 0) { break; } if (retries == 0) { first_errno = errno; } VB2_DEBUG("TPM: read attempt %d failed: %s\n", retries + 1, strerror(errno)); } if (n == 0) { return DoError(TPM_E_READ_EMPTY, "null read from TPM device\n"); } else if (n < 0) { return DoError(TPM_E_READ_FAILURE, "read failure from TPM device: %s " "(first error %d)\n", strerror(errno), first_errno); } else { if (n > *pout_len) { return DoError(TPM_E_RESPONSE_TOO_LARGE, "TPM response too long for " "output buffer\n"); } else { *pout_len = n; memcpy(out, response, n); } } } return TPM_SUCCESS; } /* Gets the tag field of a TPM command. */ __attribute__((unused)) static inline int TpmTag(const uint8_t* buffer) { uint16_t tag; FromTpmUint16(buffer, &tag); return (int) tag; } /* Gets the size field of a TPM command. */ __attribute__((unused)) static inline int TpmResponseSize(const uint8_t* buffer) { uint32_t size; FromTpmUint32(buffer + sizeof(uint16_t), &size); return (int) size; } vb2_error_t VbExTpmInit(void) { char *no_exit = getenv("TPM_NO_EXIT"); if (no_exit) exit_on_failure = !atoi(no_exit); return VbExTpmOpen(); } vb2_error_t VbExTpmClose(void) { if (tpm_fd != -1) { close(tpm_fd); tpm_fd = -1; } return VB2_SUCCESS; } vb2_error_t VbExTpmOpen(void) { const char *device_path; struct timespec delay; int retries, saved_errno; if (tpm_fd >= 0) return VB2_SUCCESS; /* Already open */ device_path = getenv("TPM_DEVICE_PATH"); if (device_path == NULL) { device_path = TPM_DEVICE_PATH; } /* Retry TPM opens on EBUSY failures. */ for (retries = 0; retries < OPEN_RETRY_MAX_NUM; ++ retries) { errno = 0; tpm_fd = open(device_path, O_RDWR | O_CLOEXEC); saved_errno = errno; if (tpm_fd >= 0) return VB2_SUCCESS; if (saved_errno != EBUSY) break; VB2_DEBUG("TPM: retrying %s: %s\n", device_path, strerror(errno)); /* Stall until TPM comes back. */ delay.tv_sec = 0; delay.tv_nsec = OPEN_RETRY_DELAY_NS; nanosleep(&delay, NULL); } return DoError(VB2_ERROR_UNKNOWN, "TPM: Cannot open TPM device %s: %s\n", device_path, strerror(saved_errno)); } uint32_t VbExTpmSendReceive(const uint8_t* request, uint32_t request_length, uint8_t* response, uint32_t* response_length) { /* * In a real firmware implementation, this function should contain * the equivalent API call for the firmware TPM driver which takes a * raw sequence of bytes as input command and a pointer to the * output buffer for putting in the results. * * For EFI firmwares, this can make use of the EFI TPM driver as * follows (based on page 16, of TCG EFI Protocol Specs Version 1.20 * availaible from the TCG website): * * EFI_STATUS status; * status = TcgProtocol->EFI_TCG_PASS_THROUGH_TO_TPM( * TpmCommandSize(request), * request, * max_length, * response); * // Error checking depending on the value of the status above */ #ifndef NDEBUG int tag, response_tag; #endif uint32_t result; #ifdef VBOOT_DEBUG struct timeval before, after; VB2_DEBUG("request (%d bytes):\n", request_length); DbgPrintBytes(request, request_length); gettimeofday(&before, NULL); #endif result = TpmExecute(request, request_length, response, response_length); if (result != TPM_SUCCESS) return result; #ifdef VBOOT_DEBUG gettimeofday(&after, NULL); VB2_DEBUG("response (%d bytes):\n", *response_length); DbgPrintBytes(response, *response_length); VB2_DEBUG("execution time: %dms\n", (int) ((after.tv_sec - before.tv_sec) * VB_MSEC_PER_SEC + (after.tv_usec - before.tv_usec) / VB_USEC_PER_MSEC)); #endif #ifndef NDEBUG /* sanity checks */ tag = TpmTag(request); response_tag = TpmTag(response); assert( (tag == TPM_TAG_RQU_COMMAND && response_tag == TPM_TAG_RSP_COMMAND) || (tag == TPM_TAG_RQU_AUTH1_COMMAND && response_tag == TPM_TAG_RSP_AUTH1_COMMAND) || (tag == TPM_TAG_RQU_AUTH2_COMMAND && response_tag == TPM_TAG_RSP_AUTH2_COMMAND)); assert(*response_length == TpmResponseSize(response)); #endif return TPM_SUCCESS; } vb2_error_t VbExTpmGetRandom(uint8_t *buf, uint32_t length) { static int urandom_fd = -1; if (urandom_fd < 0) { urandom_fd = open("/dev/urandom", O_RDONLY); if (urandom_fd == -1) { return VB2_ERROR_UNKNOWN; } } if (length != read(urandom_fd, buf, length)) { return VB2_ERROR_UNKNOWN; } return VB2_SUCCESS; }