Files
OpenCellular/common/ccd_config.c
Vadim Bendebury b95b487cbc ccd: use async TPM reset where required
When TPM is wiped out on 'ccd open', the TPM reset could be invoked on
the TPM task context, if physical presence verification was not
required, or on the hooks task context, if PP was required.

This patch makes sure that the proper TPM reset is invoked depending
on the context. Also fixing the return value in ccd_command_wrapper(),
because it is expected to be from the ec_error_list enun, and this is
what is returned in the vendor command error response payload.

BRANCH=cr50
BUG=b:62537474
TEST=verified that TPM and device reset happen smoothly in both cases
     when 'ccd open' requires and does not require PP.

Change-Id: I1935fc90b386bb8f2158001e153da371fca22d03
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/861206
Reviewed-by: Randall Spangler <rspangler@chromium.org>
2018-01-12 03:27:09 -08:00

1583 lines
38 KiB
C

/* Copyright 2017 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.
*
* Case Closed Debug configuration
*/
#include "common.h"
#include "byteorder.h"
#include "ccd_config.h"
#include "console.h"
#include "cryptoc/sha256.h"
#include "cryptoc/util.h"
#include "dcrypto.h"
#include "extension.h"
#include "hooks.h"
#include "nvmem_vars.h"
#include "physical_presence.h"
#include "shared_mem.h"
#include "system.h"
#include "system_chip.h"
#include "task.h"
#include "timer.h"
#include "tpm_registers.h"
#include "tpm_vendor_cmds.h"
#include "trng.h"
#define CPRINTS(format, args...) cprints(CC_CCD, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_CCD, format, ## args)
/* Restriction state for ccdunlock when no password is set */
enum ccd_unlock_restrict {
/* Unrestricted */
CCD_UNLOCK_UNRESTRICTED = 0,
/* Physical presence required for unlock unless disabled by config */
CCD_UNLOCK_NEED_PP,
/* Unlock not allowed */
CCD_UNLOCK_DISABLED
};
/* Minimum time between password attempts */
#define PASSWORD_RATE_LIMIT_US (3 * SECOND)
/* Current version of case-closed debugging configuration struct */
#define CCD_CONFIG_VERSION 0x10
/* Capability states */
enum ccd_capability_state {
/* Default value */
CCD_CAP_STATE_DEFAULT = 0,
/* Always available (state >= CCD_STATE_LOCKED) */
CCD_CAP_STATE_ALWAYS = 1,
/* Unless locked (state >= CCD_STATE_UNLOCKED) */
CCD_CAP_STATE_UNLESS_LOCKED = 2,
/* Only if opened (state >= CCD_STATE_OPENED) */
CCD_CAP_STATE_IF_OPENED = 3,
/* Number of capability states */
CCD_CAP_STATE_COUNT
};
/*
* CCD command header; including the subcommand code used to demultiplex
* various CCD commands over the same TPM vendor command.
*/
struct ccd_vendor_cmd_header {
struct tpm_cmd_header tpm_header;
uint8_t ccd_subcommand;
} __packed;
/* Size of password salt and digest in bytes */
#define CCD_PASSWORD_SALT_SIZE 4
#define CCD_PASSWORD_DIGEST_SIZE 16
/* Way longer than practical. */
#define CCD_MAX_PASSWORD_SIZE 40
struct ccd_config {
/* Version (CCD_CONFIG_VERSION) */
uint8_t version;
/*
* Flags. These MUST immediately follow version, so that the test
* lab flag is always the LSBit of the first flags byte.
*/
uint8_t flags[3];
/* Capabilities */
uint8_t capabilities[8];
/* Password salt (random) */
uint8_t password_salt[CCD_PASSWORD_SALT_SIZE];
/*
* Password digest = truncated
* SHA256_digest(password_salt+device_id+password)
*/
uint8_t password_digest[CCD_PASSWORD_DIGEST_SIZE];
};
struct ccd_capability_info {
/* Capability name */
const char *name;
/* Default state, if config set to CCD_CAP_STATE_DEFAULT */
enum ccd_capability_state default_state;
};
/* Nvmem variable name for CCD config */
static const uint8_t k_ccd_config = NVMEM_VAR_CCD_CONFIG;
/* Flags which can be set via ccd_set_flag() */
static const uint32_t k_public_flags =
CCD_FLAG_OVERRIDE_WP_AT_BOOT |
CCD_FLAG_OVERRIDE_WP_STATE_ENABLED;
/* List of CCD capability info; must be in same order as enum ccd_capability */
static const struct ccd_capability_info cap_info[CCD_CAP_COUNT] = {
{"UartGscRxAPTx", CCD_CAP_STATE_ALWAYS},
{"UartGscTxAPRx", CCD_CAP_STATE_ALWAYS},
{"UartGscRxECTx", CCD_CAP_STATE_ALWAYS},
{"UartGscTxECRx", CCD_CAP_STATE_IF_OPENED},
{"FlashAP", CCD_CAP_STATE_IF_OPENED},
{"FlashEC", CCD_CAP_STATE_IF_OPENED},
{"OverrideWP", CCD_CAP_STATE_IF_OPENED},
{"RebootECAP", CCD_CAP_STATE_IF_OPENED},
{"GscFullConsole", CCD_CAP_STATE_IF_OPENED},
{"UnlockNoReboot", CCD_CAP_STATE_ALWAYS},
{"UnlockNoShortPP", CCD_CAP_STATE_ALWAYS},
{"OpenNoTPMWipe", CCD_CAP_STATE_IF_OPENED},
{"OpenNoLongPP", CCD_CAP_STATE_IF_OPENED},
{"BatteryBypassPP", CCD_CAP_STATE_ALWAYS},
{"UpdateNoTPMWipe", CCD_CAP_STATE_ALWAYS},
{"I2C", CCD_CAP_STATE_IF_OPENED},
};
static const char *ccd_state_names[CCD_STATE_COUNT] = {
"Locked", "Unlocked", "Opened"};
static const char *ccd_cap_state_names[CCD_CAP_STATE_COUNT] = {
"Default", "Always", "UnlessLocked", "IfOpened"};
static enum ccd_state ccd_state = CCD_STATE_LOCKED;
static struct ccd_config config;
static uint8_t ccd_config_loaded;
static uint8_t force_disabled;
static struct mutex ccd_config_mutex;
static uint8_t ccd_console_active; /* CCD console command is in progress. */
/******************************************************************************/
/* Raw config accessors */
/**
* Get CCD flags.
*
* @return the current flags mask.
*/
static uint32_t raw_get_flags(void)
{
return (uint32_t)(config.flags[0] << 0)
| ((uint32_t)config.flags[1] << 8)
| ((uint32_t)config.flags[2] << 16);
}
/**
* Set a single CCD flag.
*
* This does NOT call ccd_save_config() or lock the mutex. Caller must do
* those.
*
* @param flag Flag to set
* @param value New value for flag (0=clear, non-zero=set)
*/
static void raw_set_flag(enum ccd_flag flag, int value)
{
uint32_t f;
f = raw_get_flags();
if (value)
f |= flag;
else
f &= ~flag;
config.flags[0] = (uint8_t)(f >> 0);
config.flags[1] = (uint8_t)(f >> 8);
config.flags[2] = (uint8_t)(f >> 16);
}
/**
* Get a raw capability state from the config
*
* @param cap Capability to check
* @param translate_default If non-zero, translate CCD_CAP_STATE_DEFAULT
* to the actual default for that config
* @return The capability state.
*/
static enum ccd_capability_state raw_get_cap(enum ccd_capability cap,
int translate_default)
{
int c = (config.capabilities[cap / 4] >> (2 * (cap % 4))) & 3;
if (c == CCD_CAP_STATE_DEFAULT && translate_default)
c = cap_info[cap].default_state;
return c;
}
/**
* Set a raw capability to the config.
*
* This does NOT call ccd_save_config() or lock the mutex. Caller must do
* those.
*
* @param cap Capability to set
* @param state New state for capability
*/
static void raw_set_cap(enum ccd_capability cap,
enum ccd_capability_state state)
{
config.capabilities[cap / 4] &= ~(3 << (2 * (cap % 4)));
config.capabilities[cap / 4] |= (state & 3) << (2 * (cap % 4));
}
/**
* Check if a password is set.
* @return 1 if password is set, 0 if it's not
*/
static int raw_has_password(void)
{
uint8_t set = 0;
int i;
/* Password is set unless salt and digest are all zero */
for (i = 0; i < sizeof(config.password_salt); i++)
set |= config.password_salt[i];
for (i = 0; i < sizeof(config.password_digest); i++)
set |= config.password_digest[i];
return !!set;
}
/**
* Calculate the expected digest for a password.
*
* Uses the unique device ID and the salt from the config.
*
* @param digest Pointer to a CCD_PASSWORD_DIGEST_SIZE buffer
* @param password The password to digest
*/
static void ccd_password_digest(uint8_t *digest, const char *password)
{
HASH_CTX sha;
uint8_t *unique_id;
int unique_id_len;
unique_id_len = system_get_chip_unique_id(&unique_id);
DCRYPTO_SHA256_init(&sha, 0);
HASH_update(&sha, config.password_salt, sizeof(config.password_salt));
HASH_update(&sha, unique_id, unique_id_len);
HASH_update(&sha, password, strlen(password));
memcpy(digest, HASH_final(&sha), CCD_PASSWORD_DIGEST_SIZE);
}
/**
* Check the password.
*
* @param password The password to check
* @return EC_SUCCESS, EC_ERROR_BUSY if too soon since last attempt, or
* EC_ERROR_ACCESS_DENIED if mismatch.
*/
static int raw_check_password(const char *password)
{
/*
* Time of last password attempt; initialized to 0 at boot. Yes, we're
* only keeping the bottom 32 bits of the timer here, so on a
* wraparound (every ~4000 seconds) it's possible for an attacker to
* get one extra attempt. But it still behaves properly at boot,
* requiring the system to be up PASSWORD_RATE_LIMIT_US before allowing
* the first attempt.
*/
static uint32_t last_password_time;
uint8_t digest[CCD_PASSWORD_DIGEST_SIZE];
uint32_t t;
/* If no password is set, match only an empty password */
if (!raw_has_password())
return *password ? EC_ERROR_ACCESS_DENIED : EC_SUCCESS;
/* Rate limit password attempts */
t = get_time().le.lo;
if (t - last_password_time < PASSWORD_RATE_LIMIT_US)
return EC_ERROR_BUSY;
last_password_time = t;
/* Calculate the digest of the password */
ccd_password_digest(digest, password);
if (safe_memcmp(digest, config.password_digest,
sizeof(config.password_digest)))
return EC_ERROR_ACCESS_DENIED;
return EC_SUCCESS;
}
/**
* Clear the password.
*
* This does NOT call ccd_save_config() or lock the mutex. Caller must do
* those.
*/
static void raw_reset_password(void)
{
memset(config.password_salt, 0, sizeof(config.password_salt));
memset(config.password_digest, 0, sizeof(config.password_digest));
raw_set_flag(CCD_FLAG_PASSWORD_SET_WHEN_UNLOCKED, 0);
}
/**
* Set the password.
*
* @param password New password; must be non-empty
*/
static void raw_set_password(const char *password)
{
/* Get a new salt */
rand_bytes(config.password_salt, sizeof(config.password_salt));
/* Update the password digest */
ccd_password_digest(config.password_digest, password);
/* Track whether we were opened when we set the password */
raw_set_flag(CCD_FLAG_PASSWORD_SET_WHEN_UNLOCKED,
ccd_state == CCD_STATE_UNLOCKED);
}
/******************************************************************************/
/* Internal methods */
/**
* Set the CCD state.
*
* @param state New CCD state
*/
static void ccd_set_state(enum ccd_state state)
{
if (state == ccd_state)
return;
ccd_state = state;
/* Notify CCD users of configuration change */
hook_notify(HOOK_CCD_CHANGE);
}
/**
* Load CCD config from nvmem_vars
*
* @return EC_SUCCESS or non-zero error code.
*/
static void ccd_load_config(void)
{
const struct tuple *t;
/* Don't reload if we're already loaded */
if (ccd_config_loaded)
return;
/* Load config data from nvmem */
t = getvar(&k_ccd_config, sizeof(k_ccd_config));
/* Use defaults if config data is not present */
if (!t) {
if (board_is_first_factory_boot()) {
/* Give factory RMA access */
CPRINTS("CCD using factory config");
ccd_reset_config(CCD_RESET_TEST_LAB | CCD_RESET_RMA);
} else {
/* Somehow we lost our config; normal defaults */
CPRINTS("CCD using default config");
ccd_reset_config(CCD_RESET_TEST_LAB);
}
ccd_config_loaded = 1;
return;
}
/* Copy the tuple data */
memcpy(&config, tuple_val(t), MIN(sizeof(config), t->val_len));
/* If version or size is wrong, reset to defaults */
if (config.version != CCD_CONFIG_VERSION ||
t->val_len != sizeof(config)) {
CPRINTS("CCD config mismatch; using defaults");
/*
* If the config data was big enough to hold the test lab bit,
* preserve it. That's guaranteed to be in the same place for
* all data versions.
*/
ccd_reset_config(t->val_len < 2 ? CCD_RESET_TEST_LAB : 0);
}
ccd_config_loaded = 1;
/* Notify CCD users of configuration change */
hook_notify(HOOK_CCD_CHANGE);
}
/**
* Save CCD config to nvmem_vars
*
* @return EC_SUCCESS or non-zero error code.
*/
static int ccd_save_config(void)
{
int rv;
rv = setvar(&k_ccd_config, sizeof(k_ccd_config),
(const uint8_t *)&config, sizeof(config));
if (rv)
return rv;
rv = writevars();
/* Notify CCD users of configuration change */
hook_notify(HOOK_CCD_CHANGE);
return rv;
}
/**
* Set a CCD capability to a new state.
*
* @param cap Capability to set
* @param state New state for capability
* @return EC_SUCCESS or non-zero error code.
*/
static int ccd_set_cap(enum ccd_capability cap, enum ccd_capability_state state)
{
if (!ccd_config_loaded)
return EC_ERROR_BUSY;
if (state == raw_get_cap(cap, 0))
return EC_SUCCESS; /* Capability not changed */
mutex_lock(&ccd_config_mutex);
raw_set_cap(cap, state);
mutex_unlock(&ccd_config_mutex);
return ccd_save_config();
}
int ccd_reset_config(unsigned int flags)
{
int old_lab = ccd_get_flag(CCD_FLAG_TEST_LAB);
mutex_lock(&ccd_config_mutex);
if (flags & CCD_RESET_UNLOCKED_ONLY) {
/* Only set config options that are mutable when unlocked */
int i;
/* Reset the password if it was set when unlocked */
if (ccd_get_flag(CCD_FLAG_PASSWORD_SET_WHEN_UNLOCKED))
raw_reset_password();
/* Reset all capabilities that aren't IfOpened */
for (i = 0; i < CCD_CAP_COUNT; i++) {
if (raw_get_cap(i, 1) == CCD_CAP_STATE_IF_OPENED)
continue;
raw_set_cap(i, CCD_CAP_STATE_DEFAULT);
}
/* Flags all require IfOpened, so don't touch those */
} else {
/* Reset the entire config */
memset(&config, 0, sizeof(config));
config.version = CCD_CONFIG_VERSION;
}
if (flags & CCD_RESET_RMA) {
/* Force RMA settings */
int i;
/* Allow all capabilities all the time */
for (i = 0; i < CCD_CAP_COUNT; i++) {
/*
* Restricted console commands are still IfOpened, but
* that's kinda meaningless because we set a
* well-defined password below.
*/
if (i == CCD_CAP_GSC_RESTRICTED_CONSOLE)
continue;
raw_set_cap(i, CCD_CAP_STATE_ALWAYS);
}
/* Force WP disabled at boot */
raw_set_flag(CCD_FLAG_OVERRIDE_WP_AT_BOOT, 1);
raw_set_flag(CCD_FLAG_OVERRIDE_WP_STATE_ENABLED, 0);
}
/* Restore test lab flag unless explicitly resetting it */
if (!(flags & CCD_RESET_TEST_LAB))
raw_set_flag(CCD_FLAG_TEST_LAB, old_lab);
mutex_unlock(&ccd_config_mutex);
return ccd_save_config();
}
/**
* Convert a string to a capability index.
*
* @param name Capability name to find
* @return The capability index, or CCD_CAP_COUNT if error
*/
static enum ccd_capability ccd_cap_from_name(const char *name)
{
int i;
for (i = 0; i < CCD_CAP_COUNT; i++) {
if (!strcasecmp(name, cap_info[i].name))
return i;
}
return CCD_CAP_COUNT;
}
/**
* Reset the password.
*
* @return EC_SUCCESS or non-zero error code.
*/
static int ccd_reset_password(void)
{
mutex_lock(&ccd_config_mutex);
raw_reset_password();
mutex_unlock(&ccd_config_mutex);
return ccd_save_config();
}
/**
* Set the password.
*
* @param password New password; must be non-empty
* @return EC_SUCCESS or non-zero error code.
*/
static int ccd_set_password(const char *password)
{
mutex_lock(&ccd_config_mutex);
raw_set_password(password);
mutex_unlock(&ccd_config_mutex);
return ccd_save_config();
}
/******************************************************************************/
/* Handlers for state changes requiring physical presence */
/*
* Could be invoked synchronously on the TPM task context, or asynchronously,
* after physical presence is established, on the hooks task context.
*
* The appropriate TPM reset entry point needs to be invoked. Also, make sure
* that the board is always rebooted when TPM is reset.
*
* @param sync Non-zero to invoke synchronously.
*/
static void ccd_open_done(int sync)
{
int rv;
if (!ccd_is_cap_enabled(CCD_CAP_OPEN_WITHOUT_TPM_WIPE)) {
/* Can't open unless wipe succeeds */
if (sync)
rv = tpm_sync_reset(1);
else
rv = board_wipe_tpm();
if (rv != EC_SUCCESS) {
CPRINTS("CCD open TPM wipe failed");
return;
}
}
if (!ccd_is_cap_enabled(CCD_CAP_UNLOCK_WITHOUT_AP_REBOOT) ||
(!ccd_is_cap_enabled(CCD_CAP_OPEN_WITHOUT_TPM_WIPE) && sync))
board_reboot_ap();
CPRINTS("CCD opened");
ccd_set_state(CCD_STATE_OPENED);
}
static void ccd_open_done_async(void)
{
ccd_open_done(0);
}
static void ccd_unlock_done(void)
{
if (!ccd_is_cap_enabled(CCD_CAP_UNLOCK_WITHOUT_AP_REBOOT))
board_reboot_ap();
CPRINTS("CCD unlocked");
ccd_set_state(CCD_STATE_UNLOCKED);
}
static void ccd_testlab_toggle(void)
{
int v = !ccd_get_flag(CCD_FLAG_TEST_LAB);
/* Use raw_set_flag() because the test lab flag is internal */
mutex_lock(&ccd_config_mutex);
raw_set_flag(CCD_FLAG_TEST_LAB, v);
mutex_unlock(&ccd_config_mutex);
if (ccd_save_config() == EC_SUCCESS)
CPRINTS("CCD test lab mode %sbled", v ? "ena" : "disa");
else
CPRINTS("Error setting CCD test lab mode!");
}
/******************************************************************************/
/* External interface */
void ccd_config_init(enum ccd_state state)
{
/* Set initial state, after making sure it's a valid one */
if (state != CCD_STATE_UNLOCKED && state != CCD_STATE_OPENED)
state = CCD_STATE_LOCKED;
ccd_state = state;
ccd_load_config();
}
int ccd_get_flag(enum ccd_flag flag)
{
uint32_t f = raw_get_flags();
if (!ccd_config_loaded || force_disabled)
return 0;
return !!(f & flag);
}
int ccd_set_flag(enum ccd_flag flag, int value)
{
if (force_disabled)
return EC_ERROR_ACCESS_DENIED;
/* Fail if trying to set a private flag */
if (flag & ~k_public_flags)
return EC_ERROR_ACCESS_DENIED;
if (!ccd_config_loaded)
return EC_ERROR_BUSY;
if (ccd_get_flag(flag) == !!value)
return EC_SUCCESS;
mutex_lock(&ccd_config_mutex);
raw_set_flag(flag, value);
mutex_unlock(&ccd_config_mutex);
return ccd_save_config();
}
int ccd_is_cap_enabled(enum ccd_capability cap)
{
if (!ccd_config_loaded || force_disabled)
return 0;
switch (raw_get_cap(cap, 1)) {
case CCD_CAP_STATE_ALWAYS:
return 1;
case CCD_CAP_STATE_UNLESS_LOCKED:
return ccd_state != CCD_STATE_LOCKED;
case CCD_CAP_STATE_IF_OPENED:
default:
return ccd_state == CCD_STATE_OPENED;
}
}
enum ccd_state ccd_get_state(void)
{
return ccd_state;
}
void ccd_disable(void)
{
CPRINTS("CCD disabled");
force_disabled = 1;
ccd_set_state(CCD_STATE_LOCKED);
}
/******************************************************************************/
/* Console commands */
static int command_ccd_info(void)
{
int i;
ccprintf("State: %s%s\n", ccd_state_names[ccd_state],
force_disabled ? " (Disabled)" : "");
ccprintf("Password: %s\n", raw_has_password() ? "set" : "none");
ccprintf("Flags: 0x%06x\n", raw_get_flags());
ccprintf("Capabilities: %.8h\n", config.capabilities);
for (i = 0; i < CCD_CAP_COUNT; i++) {
int c = raw_get_cap(i, 0);
ccprintf(" %-15s %c %d=%s",
cap_info[i].name,
ccd_is_cap_enabled(i) ? 'Y' : '-',
c, ccd_cap_state_names[c]);
if (c == CCD_CAP_STATE_DEFAULT)
ccprintf(" (%s)",
ccd_cap_state_names[cap_info[i].default_state]);
ccprintf("\n");
cflush();
}
ccputs("Use 'ccd help' to print subcommands\n");
return EC_SUCCESS;
}
static int command_ccd_reset(int argc, char **argv)
{
int flags = 0;
if (argc > 1) {
if (!strcasecmp(argv[1], "rma"))
flags = CCD_RESET_RMA;
else
return EC_ERROR_PARAM1;
}
switch (ccd_state) {
case CCD_STATE_OPENED:
ccprintf("%sResetting all settings.\n",
flags & CCD_RESET_RMA ? "RMA " : "");
/* Note that this does not reset the testlab flag */
return ccd_reset_config(flags);
case CCD_STATE_UNLOCKED:
ccprintf("Resetting unlocked settings.\n");
return ccd_reset_config(CCD_RESET_UNLOCKED_ONLY);
default:
return EC_ERROR_ACCESS_DENIED;
}
}
static int command_ccd_set(int argc, char **argv)
{
enum ccd_capability cap;
enum ccd_capability_state old;
enum ccd_capability_state new;
/* Only works if unlocked or opened */
if (ccd_state == CCD_STATE_LOCKED)
return EC_ERROR_ACCESS_DENIED;
if (argc < 3)
return EC_ERROR_PARAM_COUNT;
/* Get capability to set */
cap = ccd_cap_from_name(argv[1]);
if (cap == CCD_CAP_COUNT)
return EC_ERROR_PARAM1;
/* Get new state */
for (new = CCD_CAP_STATE_DEFAULT; new < CCD_CAP_STATE_COUNT; new++) {
if (!strcasecmp(argv[2], ccd_cap_state_names[new]))
break;
}
if (new == CCD_CAP_STATE_COUNT)
return EC_ERROR_PARAM2;
/* Get current state */
old = raw_get_cap(cap, 1);
/* If we're only unlocked, can't change to/from IfOpened */
if (ccd_state == CCD_STATE_UNLOCKED &&
(new == CCD_CAP_STATE_IF_OPENED || old == CCD_CAP_STATE_IF_OPENED))
return EC_ERROR_ACCESS_DENIED;
/* Set new state */
return ccd_set_cap(cap, new);
}
static int do_ccd_password(char *password)
{
/* Only works if unlocked or opened */
if (ccd_state == CCD_STATE_LOCKED)
return EC_ERROR_ACCESS_DENIED;
if (raw_has_password()) {
const char clear_prefix[] = {'c', 'l', 'e', 'a', 'r', ':'};
/*
* The only allowed action at this point is to clear the
* password. To do it the user is supposed to enter
* 'clear:<passwd>'
*/
if (strncasecmp(password, clear_prefix, sizeof(clear_prefix)))
return EC_ERROR_ACCESS_DENIED;
if (raw_check_password(password + sizeof(clear_prefix)) !=
EC_SUCCESS)
return EC_ERROR_ACCESS_DENIED;
return ccd_reset_password();
}
/* Set new password */
return ccd_set_password(password);
}
/*
* Common wrapper for CCD commands which are passed through the TPM task
* context.
*
* All commands could have a single parameter, which is the password (to be
* set, cleared, or entered to open/unlock). If argc calue exceeds 1, the
* pointer to password is set, it is checked not to exceed maximum size.
*
* If the check succeeds, prepare a message containing a TPM vendor command,
* have the TPM task process the message and report the result to the caller.
*
* Message header is always the same, the payload is the password, if
* supplied.
*/
static int ccd_command_wrapper(int argc, char *password,
enum ccd_vendor_subcommands subcmd)
{
int rv;
struct ccd_vendor_cmd_header *vch;
size_t command_size;
size_t password_size;
uint32_t return_code;
if (argc > 1) {
password_size = strlen(password);
if (password_size > CCD_MAX_PASSWORD_SIZE)
return EC_ERROR_PARAM1;
} else {
password_size = 0;
}
command_size = sizeof(*vch) + password_size;
rv = shared_mem_acquire(command_size, (char **)&vch);
if (rv != EC_SUCCESS)
return rv;
/* Build the extension command to set/clear CCD password. */
vch->tpm_header.tag = htobe16(0x8001); /* TPM_ST_NO_SESSIONS */
vch->tpm_header.size = htobe32(command_size);
vch->tpm_header.command_code = htobe32(TPM_CC_VENDOR_BIT_MASK);
vch->tpm_header.subcommand_code = htobe16(VENDOR_CC_CCD);
vch->ccd_subcommand = subcmd;
memcpy(vch + 1, password, password_size);
tpm_alt_extension(&vch->tpm_header, command_size);
/*
* Return status in the command code field now, in case of error,
* error code is the first byte after the header.
*/
return_code = be32toh(vch->tpm_header.command_code);
if (return_code && (return_code != VENDOR_RC_IN_PROGRESS)) {
rv = vch->ccd_subcommand;
} else {
rv = EC_SUCCESS;
}
shared_mem_release(vch);
return rv;
}
static enum vendor_cmd_rc ccd_open(void *buf,
size_t input_size,
size_t *response_size)
{
int is_long = 1;
int need_pp = 1;
int rv;
char *buffer = buf;
if (force_disabled) {
*response_size = 1;
buffer[0] = EC_ERROR_ACCESS_DENIED;
return VENDOR_RC_NOT_ALLOWED;
}
if (ccd_state == CCD_STATE_OPENED)
return VENDOR_RC_SUCCESS;
if (raw_has_password()) {
if (!input_size) {
*response_size = 1;
buffer[0] = EC_ERROR_PARAM_COUNT;
return VENDOR_RC_INTERNAL_ERROR;
}
/*
* We know there is plenty of room in the TPM buffer this is
* stored in.
*/
buffer[input_size] = '\0';
rv = raw_check_password(buffer);
if (rv) {
*response_size = 1;
buffer[0] = rv;
return VENDOR_RC_INTERNAL_ERROR;
}
} else if (!board_fwmp_allows_unlock()) {
*response_size = 1;
buffer[0] = EC_ERROR_ACCESS_DENIED;
return VENDOR_RC_NOT_ALLOWED;
}
/* Fail and abort if already checking physical presence */
if (physical_detect_busy()) {
physical_detect_abort();
*response_size = 1;
buffer[0] = EC_ERROR_BUSY;
return VENDOR_RC_INTERNAL_ERROR;
}
/* Reduce physical presence if enabled via config */
if (ccd_is_cap_enabled(CCD_CAP_OPEN_WITHOUT_LONG_PP))
is_long = 0;
if (!is_long && ccd_is_cap_enabled(CCD_CAP_UNLOCK_WITHOUT_SHORT_PP))
need_pp = 0;
/* Bypass physical presence check entirely if battery is removed */
if (ccd_is_cap_enabled(CCD_CAP_REMOVE_BATTERY_BYPASSES_PP) &&
!board_battery_is_present()) {
need_pp = 0;
}
if (need_pp) {
/* Start physical presence detect */
ccprintf("Starting CCD open...\n");
rv = physical_detect_start(is_long, ccd_open_done_async);
if (rv != EC_SUCCESS) {
*response_size = 1;
buffer[0] = rv;
return VENDOR_RC_INTERNAL_ERROR;
}
return VENDOR_RC_IN_PROGRESS;
}
/* No physical presence required; go straight to done */
ccd_open_done(1);
return VENDOR_RC_SUCCESS;
}
static enum vendor_cmd_rc ccd_unlock(void *buf,
size_t input_size,
size_t *response_size)
{
int need_pp = 1;
int rv;
char *buffer = buf;
if (force_disabled) {
*response_size = 1;
buffer[0] = EC_ERROR_ACCESS_DENIED;
return VENDOR_RC_NOT_ALLOWED;
}
if (ccd_state == CCD_STATE_UNLOCKED)
return VENDOR_RC_SUCCESS;
/* Can go from opened to unlocked with no delay or password */
if (ccd_state == CCD_STATE_OPENED) {
ccd_unlock_done();
return VENDOR_RC_SUCCESS;
}
if (raw_has_password()) {
if (!input_size) {
*response_size = 1;
buffer[0] = EC_ERROR_PARAM_COUNT;
return VENDOR_RC_INTERNAL_ERROR;
}
/*
* We know there is plenty of room in the TPM buffer this is
* stored in.
*/
buffer[input_size] = '\0';
rv = raw_check_password(buffer);
if (rv) {
*response_size = 1;
buffer[0] = rv;
return VENDOR_RC_INTERNAL_ERROR;
}
} else if (!board_fwmp_allows_unlock()) {
*response_size = 1;
buffer[0] = EC_ERROR_ACCESS_DENIED;
return VENDOR_RC_NOT_ALLOWED;
} else {
/*
* When unlock is requested via the console, physical presence
* is required unless disabled by config. This prevents a
* malicious peripheral from setitng a password.
*
* If this were a TPM vendor command from the AP, we would
* instead check unlock restrictions based on the user login
* state stored in ccd_unlock_restrict:
*
* 1) Unlock from the AP is unrestricted before any users
* login, so enrollment policy scripts can update CCD config.
*
* 2) Owner accounts can unlock, but require physical presence
* to prevent OS-level compromises from setting a password.
*
* 3) A non-owner account logging in blocks CCD config until
* the next AP reboot, as implied by TPM reboot.
*/
}
/* Fail and abort if already checking physical presence */
if (physical_detect_busy()) {
physical_detect_abort();
*response_size = 1;
buffer[0] = EC_ERROR_BUSY;
return VENDOR_RC_INTERNAL_ERROR;
}
/* Bypass physical presence check if configured to do so */
if (ccd_is_cap_enabled(CCD_CAP_UNLOCK_WITHOUT_SHORT_PP))
need_pp = 0;
/* Bypass physical presence check entirely if battery is removed */
if (ccd_is_cap_enabled(CCD_CAP_REMOVE_BATTERY_BYPASSES_PP) &&
!board_battery_is_present()) {
need_pp = 0;
}
if (need_pp) {
/* Start physical presence detect */
ccprintf("Starting CCD unlock...\n");
rv = physical_detect_start(0, ccd_unlock_done);
if (rv != EC_SUCCESS) {
*response_size = 1;
buffer[0] = rv;
return VENDOR_RC_INTERNAL_ERROR;
}
return VENDOR_RC_IN_PROGRESS;
}
/* Unlock immediately */
ccd_unlock_done();
return VENDOR_RC_SUCCESS;
}
static enum vendor_cmd_rc ccd_lock(void *unused0,
size_t unused1,
size_t *response_size)
{
/* Lock always works */
ccprintf("CCD locked.\n");
ccd_set_state(CCD_STATE_LOCKED);
return VENDOR_RC_SUCCESS;
}
/* NOTE: Testlab command is console-only; no TPM vendor command for this */
static int command_ccd_testlab(int argc, char **argv)
{
int newflag = 0;
if (force_disabled)
return EC_ERROR_ACCESS_DENIED;
if (argc < 2) {
ccprintf("CCD test lab mode %sbled\n",
ccd_get_flag(CCD_FLAG_TEST_LAB) ? "ena" : "disa");
return EC_SUCCESS;
}
if (!strcasecmp(argv[1], "open")) {
if (!ccd_get_flag(CCD_FLAG_TEST_LAB))
return EC_ERROR_ACCESS_DENIED;
/* Go directly to open state without wiping TPM or rebooting */
ccd_set_state(CCD_STATE_OPENED);
return EC_SUCCESS;
}
/* All other commands require CCD opened */
if (ccd_state != CCD_STATE_OPENED)
return EC_ERROR_ACCESS_DENIED;
if (!parse_bool(argv[1], &newflag))
return EC_ERROR_PARAM1;
if (newflag == ccd_get_flag(CCD_FLAG_TEST_LAB))
return EC_SUCCESS; /* No change */
/* If we're still here, need to toggle test lab flag */
ccprintf("Requesting change of test lab flag.\n");
if (newflag)
ccprintf("NOTE: THIS WILL MAKE THIS DEVICE INSECURE!!!\n");
return physical_detect_start(0, ccd_testlab_toggle);
}
#ifdef CONFIG_CASE_CLOSED_DEBUG_V1_UNSAFE
/**
* Test command to forcibly reset CCD config
*/
static int command_ccd_oops(void)
{
/* Completely reset CCD config and go to opened state */
force_disabled = 0;
ccprintf("Aborting physical detect...\n");
physical_detect_abort();
ccprintf("Resetting CCD config...\n");
ccd_reset_config(CCD_RESET_TEST_LAB);
ccprintf("Opening CCD...\n");
ccd_set_state(CCD_STATE_OPENED);
return EC_SUCCESS;
}
#endif /* CONFIG_CASE_CLOSED_DEBUG_V1_UNSAFE */
#ifdef CONFIG_CMD_CCD_DISABLE
static int command_ccd_disable(void)
{
ccd_disable();
return EC_SUCCESS;
}
#endif /* CONFIG_CMD_CCD_DISABLE */
static int command_ccd_help(void)
{
int i;
ccputs("usage: ccd [cmd [args]]\n\n"
"get (or just 'ccd')\n"
"\tPrint current config\n\n"
"lock\n"
"unlock [password]\n"
"open [password]\n"
"\tSet CCD state\n\n"
"set <capability> [");
cflush();
for (i = 0; i < CCD_CAP_STATE_COUNT; i++)
ccprintf("%s%s", i ? " | " : "", ccd_cap_state_names[i]);
ccputs("]\n"
"\tSet capability to state\n\n"
"password [<new password> | clear]\n"
"\tSet or clear CCD password\n\n"
"reset [rma]\n"
"\tReset CCD config\n\n"
"testlab [enable | disable | open]\n"
"\tToggle testlab mode or force CCD open\n\n");
cflush();
#ifdef CONFIG_CASE_CLOSED_DEBUG_V1_UNSAFE
ccputs("oops\n"
"\tForce-reset CCD config\n\n");
#endif
#ifdef CONFIG_CMD_CCD_DISABLE
ccputs("disable\n"
"\tTemporarily disable CCD\n\n");
#endif
return EC_SUCCESS;
}
/**
* Case closed debugging config command.
*/
static int command_ccd_body(int argc, char **argv)
{
/* If no args or 'get', print info */
if (argc < 2 || !strcasecmp(argv[1], "get"))
return command_ccd_info();
/* Check test lab command first */
if (!strcasecmp(argv[1], "testlab"))
return command_ccd_testlab(argc - 1, argv + 1);
/* Commands to set state */
if (!strcasecmp(argv[1], "lock"))
return ccd_command_wrapper(0, NULL, CCDV_LOCK);
if (!strcasecmp(argv[1], "unlock")) {
if (!raw_has_password()) {
ccprintf("Unlock only allowed after password is set\n");
return EC_ERROR_ACCESS_DENIED;
}
return ccd_command_wrapper(argc - 1, argv[2], CCDV_UNLOCK);
}
if (!strcasecmp(argv[1], "open"))
return ccd_command_wrapper(argc - 1, argv[2], CCDV_OPEN);
/* Commands to configure capabilities */
if (!strcasecmp(argv[1], "set"))
return command_ccd_set(argc - 1, argv + 1);
if (!strcasecmp(argv[1], "password")) {
if (argc != 3)
return EC_ERROR_PARAM_COUNT;
return ccd_command_wrapper(argc - 1, argv[2], CCDV_PASSWORD);
}
if (!strcasecmp(argv[1], "reset"))
return command_ccd_reset(argc - 1, argv + 1);
/* Optional commands */
#ifdef CONFIG_CASE_CLOSED_DEBUG_V1_UNSAFE
if (!strcasecmp(argv[1], "oops"))
return command_ccd_oops();
#endif
#ifdef CONFIG_CMD_CCD_DISABLE
if (!strcasecmp(argv[1], "disable"))
return command_ccd_disable();
#endif
/* Anything else (including "help") prints help */
return command_ccd_help();
}
static int command_ccd(int argc, char **argv)
{
int rv;
ccd_console_active = 1;
rv = command_ccd_body(argc, argv);
ccd_console_active = 0;
return rv;
}
DECLARE_SAFE_CONSOLE_COMMAND(ccd, command_ccd,
"[help | ...]",
"Configure case-closed debugging");
/*
* Password handling on Cr50 passes the following states:
*
* - password setting is not allowed after Cr50 reset until an upstart (as
* opposed to resume) TPM startup happens, as signalled by the TPM callback.
* After the proper TPM reset the state changes to 'POST_RESET_STATE' which
* means that the device was just reset/rebooted (not resumed) and no user
* logged in yet.
*
* - if the owner logs in in this state, the state changes to
* 'PASSWORD_ALLOWED_STATE'. The owner can open crosh session and set the
* password.
*
* - when the owner logs out or any user but the owner logs in, the state
* changes to PASSWORD_NOT_ALLOWED_STATE and does not change until TPM is
* reset. This makes sure that password can be set only by the owner and
* only before anybody else logged in.
*/
enum password_reset_phase {
POST_RESET_STATE,
PASSWORD_ALLOWED_STATE,
PASSWORD_NOT_ALLOWED_STATE
};
static uint8_t password_state = PASSWORD_NOT_ALLOWED_STATE;
void ccd_tpm_reset_callback(void)
{
CPRINTS("%s: TPM Startup processed", __func__);
password_state = POST_RESET_STATE;
}
/*
* Handle the VENDOR_CC_MANAGE_CCD_PASSWORD command.
*
* The payload of the command is a single byte Boolean which sets the controls
* if CCD password can be set or not.
*
* After reset the pasword can not be set using VENDOR_CC_CCD_PASSWORD; once
* this command is received with value of True, the phase stars when the
* password can be set. As soon as this command is received with a value of
* False, the password can not be set any more until device is rebooted, even
* if this command is re-sent with the value of True.
*/
static enum vendor_cmd_rc manage_ccd_password(enum vendor_cmd_cc code,
void *buf,
size_t input_size,
size_t *response_size)
{
uint8_t prev_state = password_state;
/* The vendor command status code. */
enum vendor_cmd_rc rv = VENDOR_RC_SUCCESS;
/* Actual error code. */
uint8_t error_code = EC_SUCCESS;
do {
int value;
if (input_size != 1) {
rv = VENDOR_RC_INTERNAL_ERROR;
error_code = EC_ERROR_PARAM1;
break;
}
value = *((uint8_t *)buf);
if (!value) {
/* No more password setting allowed. */
password_state = PASSWORD_NOT_ALLOWED_STATE;
break;
}
if (password_state == POST_RESET_STATE) {
/* The only way to allow password setting. */
password_state = PASSWORD_ALLOWED_STATE;
break;
}
password_state = PASSWORD_NOT_ALLOWED_STATE;
rv = VENDOR_RC_BOGUS_ARGS;
error_code = EC_ERROR_INVAL;
} while (0);
if (prev_state != password_state)
CPRINTF("%s: state change from %d to %d\n",
__func__, prev_state, password_state);
if (rv == VENDOR_RC_SUCCESS) {
*response_size = 0;
} else {
*response_size = 1;
((uint8_t *)buf)[0] = error_code;
}
return rv;
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_MANAGE_CCD_PWD, manage_ccd_password);
/*
* Handle the CCVD_PASSWORD subcommand.
*
* The payload of the command is a text string to use to set or clear the
* password.
*/
static enum vendor_cmd_rc ccd_password(void *buf,
size_t input_size,
size_t *response_size)
{
int rv = EC_SUCCESS;
char password[CCD_MAX_PASSWORD_SIZE + 1];
char *response = buf;
if (password_state != PASSWORD_ALLOWED_STATE) {
*response_size = 1;
*response = EC_ERROR_ACCESS_DENIED;
return VENDOR_RC_NOT_ALLOWED;
}
if (!input_size || (input_size >= sizeof(password))) {
rv = EC_ERROR_PARAM1;
} else {
memcpy(password, buf, input_size);
password[input_size] = '\0';
rv = do_ccd_password(password);
always_memset(password, 0, input_size);
}
if (rv != EC_SUCCESS) {
*response = rv;
*response_size = 1;
return VENDOR_RC_INTERNAL_ERROR;
}
return VENDOR_RC_SUCCESS;
}
static enum vendor_cmd_rc ccd_pp_poll(void *buf,
size_t input_size,
size_t *response_size)
{
char *buffer = buf;
if ((ccd_state == CCD_STATE_OPENED) ||
(ccd_state == CCD_STATE_UNLOCKED)) {
buffer[0] = CCD_PP_DONE;
} else {
switch (physical_presense_fsm_state()) {
case PP_AWAITING_PRESS:
buffer[0] = CCD_PP_AWAITING_PRESS;
break;
case PP_BETWEEN_PRESSES:
buffer[0] = CCD_PP_BETWEEN_PRESSES;
break;
default:
buffer[0] = CCD_PP_CLOSED;
break;
}
}
*response_size = 1;
return VENDOR_RC_SUCCESS;
}
static enum vendor_cmd_rc ccd_pp_poll_unlock(void *buf,
size_t input_size,
size_t *response_size)
{
char *buffer;
if ((ccd_state != CCD_STATE_OPENED) &&
(ccd_state != CCD_STATE_UNLOCKED))
return ccd_pp_poll(buf, input_size, response_size);
buffer = buf;
*response_size = 1;
buffer[0] = CCD_PP_DONE;
return VENDOR_RC_SUCCESS;
}
static enum vendor_cmd_rc ccd_pp_poll_open(void *buf,
size_t input_size,
size_t *response_size)
{
char *buffer;
if (ccd_state != CCD_STATE_OPENED)
return ccd_pp_poll(buf, input_size, response_size);
buffer = buf;
*response_size = 1;
buffer[0] = CCD_PP_DONE;
return VENDOR_RC_SUCCESS;
}
/*
* Common TPM Vendor command handler used to demultiplex various CCD commands
* which need to be available both throuh CLI and over /dev/tpm0.
*/
static enum vendor_cmd_rc ccd_vendor(enum vendor_cmd_cc code,
void *buf,
size_t input_size,
size_t *response_size)
{
enum vendor_cmd_rc (*handler)(void *x, size_t y, size_t *t);
char *buffer;
enum vendor_cmd_rc rc;
/*
* buf points to the next byte after tpm header, i.e. to the CCD
* subcommand. Cache the pointer to make it easier to access and
* manipulate.
*/
buffer = buf;
/* Pick what to do based on subcommand. */
switch (buffer[0]) {
case CCDV_PASSWORD:
handler = ccd_password;
break;
case CCDV_OPEN:
handler = ccd_open;
break;
case CCDV_UNLOCK:
handler = ccd_unlock;
break;
case CCDV_LOCK:
handler = ccd_lock;
break;
case CCDV_PP_POLL_UNLOCK:
handler = ccd_pp_poll_unlock;
break;
case CCDV_PP_POLL_OPEN:
handler = ccd_pp_poll_open;
break;
default:
CPRINTS("%s:%d - unknown subcommand\n", __func__, __LINE__);
break;
}
if (handler) {
*response_size = 0; /* Let's be optimistic: 0 means success. */
rc = handler(buf + 1, input_size - 1, response_size);
/*
* Move response up for the master to see it in the right
* place in the response buffer.
*/
memmove(buf, buf + 1, *response_size);
} else {
rc = VENDOR_RC_NO_SUCH_SUBCOMMAND;
*response_size = 0;
}
return rc;
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_CCD, ccd_vendor);
static enum vendor_cmd_rc ccd_disable_rma(enum vendor_cmd_cc code,
void *buf,
size_t input_size,
size_t *response_size)
{
int rv;
int error_line;
do {
if (raw_has_password()) {
error_line = __LINE__;
break;
}
/* Check if physical presence is required to unlock. */
if (!ccd_is_cap_enabled(CCD_CAP_REMOVE_BATTERY_BYPASSES_PP) ||
board_battery_is_present()) {
const uint8_t required_capabilities[] = {
CCD_CAP_OPEN_WITHOUT_TPM_WIPE,
CCD_CAP_UNLOCK_WITHOUT_AP_REBOOT,
CCD_CAP_OPEN_WITHOUT_LONG_PP,
CCD_CAP_UNLOCK_WITHOUT_SHORT_PP
};
unsigned int i;
for (i = 0;
i < ARRAY_SIZE(required_capabilities);
i++) {
if (!ccd_is_cap_enabled
(required_capabilities[i]))
break;
}
if (i < ARRAY_SIZE(required_capabilities)) {
CPRINTF("Capability %d is not present\n",
required_capabilities[i]);
error_line = __LINE__;
break;
}
}
ccd_set_state(CCD_STATE_OPENED);
rv = command_ccd_reset(0, NULL);
if (rv != EC_SUCCESS) {
error_line = __LINE__;
break;
}
ccd_lock(NULL, 0, NULL);
*response_size = 0;
return VENDOR_RC_SUCCESS;
} while (0);
CPRINTF("%s: error in line %d\n", __func__, error_line);
((uint8_t *)buf)[0] = (uint8_t)rv;
*response_size = 1;
return VENDOR_RC_INTERNAL_ERROR;
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_DISABLE_RMA, ccd_disable_rma);