1554 lines
38 KiB
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);
|