coreboot-libre-fam15h-rdimm/3rdparty/chromeec/common/ccd_config.c

1554 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 "system.h"
#include "system_chip.h"
#include "task.h"
#include "timer.h"
#include "tpm_registers.h"
#include "tpm_vendor_cmds.h"
#include "trng.h"
#include "wp.h"
#define CPRINTS(format, args...) cprints(CC_CCD, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_CCD, format, ## args)
/* Let's make sure that CCD capability state enum fits into two bits. */
BUILD_ASSERT(CCD_CAP_STATE_COUNT <= 4);
/* 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
/*
* 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;
/* On input, the subcommand. On output, may contain EC return code */
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];
};
/* 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 |
CCD_FLAG_OVERRIDE_BATT_AT_BOOT |
CCD_FLAG_OVERRIDE_BATT_STATE_CONNECT;
/* 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] = CAP_INFO_DATA;
static const char *ccd_state_names[CCD_STATE_COUNT] = CCD_STATE_NAMES;
static const char *ccd_cap_state_names[CCD_CAP_STATE_COUNT] =
CCD_CAP_STATE_NAMES;
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)
{
const uint32_t index = cap / CCD_CAPS_PER_BYTE;
const uint32_t shift = (cap % CCD_CAPS_PER_BYTE) * CCD_CAP_BITS;
int c = (config.capabilities[index] >> shift) & CCD_CAP_BITMASK;
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)
{
const uint32_t index = cap / CCD_CAPS_PER_BYTE;
const uint32_t shift = (cap % CCD_CAPS_PER_BYTE) * CCD_CAP_BITS;
config.capabilities[index] &= ~(CCD_CAP_BITMASK << shift);
config.capabilities[index] |= (state & CCD_CAP_BITMASK) << shift;
}
/**
* Check CCD configuration is reset to default value.
*
* @return 1 if it is in default mode.
* 0 otherwise.
*/
static int raw_check_all_caps_default(void)
{
uint32_t i;
for (i = 0; i < CCD_CAP_COUNT; i++)
if (raw_get_cap(i, 0) != CCD_CAP_STATE_DEFAULT)
return 0;
return 1;
}
/**
* 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_FACTORY);
} else {
/* Somehow we lost our config; normal defaults */
CPRINTS("CCD using default config");
ccd_reset_config(CCD_RESET_TEST_LAB);
}
goto ccd_is_loaded;
}
/* 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);
}
freevar(t);
ccd_is_loaded:
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;
/*
* Notify CCD users of configuration change.
* Protect this notify with the ccd_config_loaded flag so recipients of
* HOOK_CCD_CHANGE don't call ccd_get/ccd_set before the CCD
* initialization is complete.
*/
if (ccd_config_loaded)
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;
/* Update write protect after resetting the config */
board_wp_follow_ccd_config();
}
if (flags & CCD_RESET_FACTORY) {
/* Force factory mode settings */
int i;
/* Allow all capabilities all the time */
for (i = 0; i < CCD_CAP_COUNT; i++)
raw_set_cap(i, CCD_CAP_STATE_ALWAYS);
raw_set_flag(CCD_FLAG_FACTORY_MODE_ENABLED, 1);
/* 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);
board_wp_follow_ccd_config();
}
/* 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;
/*
* Wiping the TPM may take a while. Delay sleep long enough for the
* open process to finish.
*/
delay_sleep_by(DISABLE_SLEEP_TIME_TPM_WIPE);
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(1);
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 */
int ccd_has_password(void)
{
return raw_has_password();
}
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);
}
int ccd_get_factory_mode(void)
{
return ccd_get_flag(CCD_FLAG_FACTORY_MODE_ENABLED);
}
/******************************************************************************/
/* 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();
}
ccprintf("TPM:%s%s\n",
board_fwmp_allows_unlock() ? "" : " fwmp_lock",
board_vboot_dev_mode_enabled() ? " dev_mode" : "");
ccprintf("Capabilities are %s.\n", raw_check_all_caps_default() ?
"default" : "modified");
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], "factory"))
flags = CCD_RESET_FACTORY;
else
return EC_ERROR_PARAM1;
}
switch (ccd_state) {
case CCD_STATE_OPENED:
ccprintf("%s settings.\n", flags & CCD_RESET_FACTORY ?
"Opening factory " : "Resetting all");
/* 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 value 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.
*
* Expected output is nothing on success, or a single byte EC return code.
*/
static int ccd_command_wrapper(int argc, char *password,
enum ccd_vendor_subcommands subcmd)
{
uint8_t buf[sizeof(struct ccd_vendor_cmd_header) +
CCD_MAX_PASSWORD_SIZE];
struct ccd_vendor_cmd_header *vch = (struct ccd_vendor_cmd_header *)buf;
size_t password_size = 0;
uint32_t return_code;
if (argc > 1) {
password_size = strlen(password);
if (password_size > CCD_MAX_PASSWORD_SIZE)
return EC_ERROR_PARAM1;
}
/* Build the extension command to set/clear CCD password. */
vch->tpm_header.tag = htobe16(0x8001); /* TPM_ST_NO_SESSIONS */
vch->tpm_header.size = htobe32(sizeof(*vch) + password_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, sizeof(buf));
/*
* 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 != VENDOR_RC_SUCCESS) &&
(return_code != (VENDOR_RC_IN_PROGRESS|VENDOR_RC_ERR))) {
return vch->ccd_subcommand;
}
return EC_SUCCESS;
}
static enum vendor_cmd_rc ccd_open(struct vendor_cmd_params *p)
{
int is_long = 1;
int need_pp = 1;
int rv;
char *buffer = p->buffer;
const char *why_denied = "forced";
if (force_disabled)
goto denied;
if (ccd_state == CCD_STATE_OPENED)
return VENDOR_RC_SUCCESS;
/* FWMP blocks open even if a password is set */
if (!board_fwmp_allows_unlock()) {
why_denied = "fwmp";
goto denied;
}
/* Make sure open is allowed */
if (raw_has_password()) {
/* Open allowed if correct password is specified */
if (!p->in_size) {
/* ...which it wasn't */
p->out_size = 1;
buffer[0] = EC_ERROR_PARAM_COUNT;
return VENDOR_RC_PASSWORD_REQUIRED;
}
/*
* We know there is plenty of room in the TPM buffer this is
* stored in.
*/
buffer[p->in_size] = '\0';
rv = raw_check_password(buffer);
if (rv) {
p->out_size = 1;
buffer[0] = rv;
return VENDOR_RC_INTERNAL_ERROR;
}
} else if (!board_battery_is_present()) {
/* Open allowed with no password if battery is removed */
} else if ((ccd_is_cap_enabled(CCD_CAP_OPEN_WITHOUT_DEV_MODE) ||
(board_vboot_dev_mode_enabled())) &&
(ccd_is_cap_enabled(CCD_CAP_OPEN_FROM_USB) ||
!(p->flags & VENDOR_CMD_FROM_USB))) {
/*
* Open allowed with no password if dev mode enabled and
* command came from the AP. CCD capabilities can be used to
* bypass these checks.
*/
} else {
/*
* - Battery is present
* - Either not in developer mode or the command came from USB
*/
why_denied = "open from AP in devmode or remove batt";
goto denied;
}
/* Fail and abort if already checking physical presence */
if (physical_detect_busy()) {
physical_detect_abort();
p->out_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) {
p->out_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;
denied:
/* Open not allowed for some reason */
CPRINTS("%s denied: %s", __func__, why_denied);
p->out_size = 1;
buffer[0] = EC_ERROR_ACCESS_DENIED;
return VENDOR_RC_NOT_ALLOWED;
}
static enum vendor_cmd_rc ccd_unlock(struct vendor_cmd_params *p)
{
int need_pp = 1;
int rv;
char *buffer = p->buffer;
if (force_disabled) {
p->out_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;
}
/* Only allowed if password is already set, and not blocked by FWMP */
if (!raw_has_password() || !board_fwmp_allows_unlock()) {
p->out_size = 1;
buffer[0] = EC_ERROR_ACCESS_DENIED;
return VENDOR_RC_NOT_ALLOWED;
}
/* Make sure password was specified */
if (!p->in_size) {
p->out_size = 1;
buffer[0] = EC_ERROR_PARAM_COUNT;
return VENDOR_RC_PASSWORD_REQUIRED;
}
/*
* Check the password. We know there is plenty of room in the TPM
* buffer this is stored in.
*/
buffer[p->in_size] = '\0';
rv = raw_check_password(buffer);
if (rv) {
p->out_size = 1;
buffer[0] = rv;
return VENDOR_RC_INTERNAL_ERROR;
}
/* Fail and abort if already checking physical presence */
if (physical_detect_busy()) {
physical_detect_abort();
p->out_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) {
p->out_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(struct vendor_cmd_params *p)
{
/* 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 [factory]\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");
/*
* 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(struct vendor_cmd_params *p)
{
int rv = EC_SUCCESS;
char password[CCD_MAX_PASSWORD_SIZE + 1];
char *response = p->buffer;
/*
* Only allow setting a password from the AP, not USB. This increases
* the effort required for an attacker to set one externally, even if
* they have access to a system someone left in the opened state.
*
* An attacker can still set testlab mode or open up the CCD config,
* but those changes are reversible by the device owner.
*/
if (p->flags & VENDOR_CMD_FROM_USB) {
p->out_size = 1;
*response = EC_ERROR_ACCESS_DENIED;
return VENDOR_RC_NOT_ALLOWED;
}
if (!p->in_size || (p->in_size >= sizeof(password))) {
rv = EC_ERROR_PARAM1;
} else {
memcpy(password, p->buffer, p->in_size);
password[p->in_size] = '\0';
rv = do_ccd_password(password);
always_memset(password, 0, p->in_size);
}
if (rv != EC_SUCCESS) {
*response = rv;
p->out_size = 1;
return VENDOR_RC_INTERNAL_ERROR;
}
return VENDOR_RC_SUCCESS;
}
static enum vendor_cmd_rc ccd_pp_poll(struct vendor_cmd_params *p)
{
char *buffer = p->buffer;
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;
}
}
p->out_size = 1;
return VENDOR_RC_SUCCESS;
}
static enum vendor_cmd_rc ccd_pp_poll_unlock(struct vendor_cmd_params *p)
{
char *buffer = p->buffer;
if ((ccd_state != CCD_STATE_OPENED) &&
(ccd_state != CCD_STATE_UNLOCKED))
return ccd_pp_poll(p);
p->out_size = 1;
buffer[0] = CCD_PP_DONE;
return VENDOR_RC_SUCCESS;
}
static enum vendor_cmd_rc ccd_pp_poll_open(struct vendor_cmd_params *p)
{
char *buffer = p->buffer;
if (ccd_state != CCD_STATE_OPENED)
return ccd_pp_poll(p);
p->out_size = 1;
buffer[0] = CCD_PP_DONE;
return VENDOR_RC_SUCCESS;
}
static enum vendor_cmd_rc ccd_get_info(struct vendor_cmd_params *p)
{
int i;
struct ccd_info_response response = {};
for (i = 0; i < CCD_CAP_COUNT; i++) {
int index;
int shift;
/* Each capability takes 2 bits. */
index = i / (32 / CCD_CAP_BITS);
shift = (i % (32 / CCD_CAP_BITS)) * CCD_CAP_BITS;
response.ccd_caps_current[index] |= raw_get_cap(i, 1) << shift;
response.ccd_caps_defaults[index] |=
cap_info[i].default_state << shift;
}
response.ccd_flags = htobe32(raw_get_flags());
response.ccd_state = ccd_get_state();
response.ccd_indicator_bitmap = raw_has_password() ?
CCD_INDICATOR_BIT_HAS_PASSWORD : 0;
response.ccd_indicator_bitmap |= raw_check_all_caps_default() ?
CCD_INDICATOR_BIT_ALL_CAPS_DEFAULT : 0;
response.ccd_force_disabled = force_disabled;
for (i = 0; i < ARRAY_SIZE(response.ccd_caps_current); i++) {
response.ccd_caps_current[i] =
htobe32(response.ccd_caps_current[i]);
response.ccd_caps_defaults[i] =
htobe32(response.ccd_caps_defaults[i]);
}
p->out_size = sizeof(response);
memcpy(p->buffer, &response, sizeof(response));
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(struct vendor_cmd_params *p)
{
enum vendor_cmd_rc (*handler)(struct vendor_cmd_params *p);
enum vendor_cmd_rc rc;
/*
* The command buffer 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.
*/
char *buffer = p->buffer;
/*
* Make sure the buffer is large enough to accommodate any CCD
* subcommand response (plus one byte, since the response is shifted),
* so we can skip size checks in the processing functions.
*/
if (p->out_size < sizeof(struct ccd_info_response) + 1) {
p->out_size = 0;
return VENDOR_RC_RESPONSE_TOO_BIG;
}
/* Now we can assume no output data unless proven otherwise */
p->out_size = 0;
/* 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;
case CCDV_GET_INFO:
handler = ccd_get_info;
break;
default:
CPRINTS("%s:%d - unknown subcommand", __func__, __LINE__);
return VENDOR_RC_NO_SUCH_SUBCOMMAND;
}
/* Shift buffer past the subcommand when calling the handler */
p->buffer = buffer + 1;
p->in_size--;
rc = handler(p);
p->buffer = buffer;
p->in_size++;
/*
* Move response up for the master to see it in the right
* place in the response buffer. We have to do this because the
* first byte of the buffer on input was the subcommand, so we
* passed buffer + 1 in the handler call above.
*/
memmove(buffer, buffer + 1, p->out_size);
return rc;
}
DECLARE_VENDOR_COMMAND_P(VENDOR_CC_CCD, ccd_vendor);
static enum vendor_cmd_rc ccd_disable_factory_mode(enum vendor_cmd_cc code,
void *buf,
size_t input_size,
size_t *response_size)
{
int rv = EC_SUCCESS;
int error_line;
do {
if (raw_has_password()) {
error_line = __LINE__;
rv = EC_ERROR_ACCESS_DENIED;
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__;
rv = EC_ERROR_ACCESS_DENIED;
break;
}
}
ccd_set_state(CCD_STATE_OPENED);
rv = command_ccd_reset(0, NULL);
if (rv != EC_SUCCESS) {
error_line = __LINE__;
break;
}
ccd_lock(NULL);
/*
* We do it here to make sure that the device comes out of
* factory mode with WP enabled, but in general CCD reset needs
* to enforce WP state.
*
* TODO(rspangler): sort out CCD state and WP correlation,
* b/73075443.
*/
board_wp_follow_ccd_config();
/*
* Use raw_set_flag() because the factory mode flag is internal
*/
mutex_lock(&ccd_config_mutex);
raw_set_flag(CCD_FLAG_FACTORY_MODE_ENABLED, 0);
mutex_unlock(&ccd_config_mutex);
*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_FACTORY, ccd_disable_factory_mode);