Files
xen/stubdom/vtpmmgr/vtpm_cmd_handler.c
T
Jason Andryuk 5974702bdd vtpmmgr: Check req_len before unpacking command
vtpm_handle_cmd doesn't ensure there is enough space before unpacking
the req buffer.  Add a minimum size check.  Called functions will have
to do their own checking if they need more data from the request.

The error case is tricky since abort_egress wants to rely with a
corresponding tag.  Just hardcode TPM_TAG_RQU_COMMAND since the vtpm is
sending in malformed commands in the first place.

Signed-off-by: Jason Andryuk <jandryuk@gmail.com>
Reviewed-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Daniel P. Smith <dpsmith@apertussolutions.com>
2021-05-10 14:50:33 +01:00

948 lines
24 KiB
C

/*
* Copyright (c) 2010-2012 United States Government, as represented by
* the Secretary of Defense. All rights reserved.
*
* based off of the original tools/vtpm_manager code base which is:
* Copyright (c) 2005, Intel Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <mini-os/console.h>
#include <mini-os/lib.h>
#include <polarssl/sha1.h>
#include <polarssl/sha2.h>
#include "marshal.h"
#include "log.h"
#include "vtpm_disk.h"
#include "vtpmmgr.h"
#include "tpm.h"
#include "tpm2.h"
#include "tpmrsa.h"
#include "tcg.h"
#include "mgmt_authority.h"
#include "disk_crypto.h"
static void gen_random_uuid(uuid_t uuid)
{
do_random(uuid, 16);
// make the 128-bit random number a valid UUID (122 bits remain)
uuid[6] = 0x40 | (uuid[6] & 0x0F);
uuid[8] = 0x80 | (uuid[8] & 0x3F);
}
/*
* Instead of using a kernel hash, which requires a trusted domain builder to
* report, use the XSM label as a substitute.
*/
static TPM_RESULT find_vtpm_khash(int domid, struct tpm_opaque *opq)
{
char buf[128];
int i, rv;
buf[127] = 0;
rv = tpmback_get_peercontext(opq->domid, opq->handle, buf, sizeof(buf) - 1);
if (rv < 0)
return TPM_FAIL;
sha1((void*)buf, strlen(buf), opq->kern_hash);
/*
* As a hack to support the use of the XSM user field as an optional
* wildcard, check the hash against the group here. If it fails, replace
* the user field with a "*" and return the hash of that value.
*/
for(i=0; i < be32_native(opq->group->seal_bits.nr_kerns); i++) {
if (!memcmp(opq->group->seal_bits.kernels[i].bits, opq->kern_hash, 20)) {
return TPM_SUCCESS;
}
}
char* upos = strchr(buf, ':');
if (upos == NULL || upos == buf)
return TPM_SUCCESS;
upos--;
upos[0] = '*';
sha1((void*)upos, strlen(upos), opq->kern_hash);
return TPM_SUCCESS;
}
static TPM_RESULT find_vtpm_verified(int domid, struct tpm_opaque *opq)
{
TPM_RESULT rc;
int i;
if (opq->vtpm)
return TPM_SUCCESS;
rc = find_vtpm(&opq->group, &opq->vtpm, *opq->uuid);
if (rc)
return TPM_BAD_PARAMETER;
if (opq->vtpm->flags & VTPM_FLAG_OPEN) {
printk("Attempted to open vTPM twice!\n");
opq->vtpm = NULL;
opq->group = NULL;
return TPM_DISABLED;
}
rc = find_vtpm_khash(domid, opq);
if (rc)
return rc;
for(i=0; i < be32_native(opq->group->seal_bits.nr_kerns); i++) {
if (!memcmp(opq->group->seal_bits.kernels[i].bits, opq->kern_hash, 20)) {
opq->vtpm->flags |= VTPM_FLAG_OPEN;
return TPM_SUCCESS;
}
}
printk("Unauthorized vTPM kernel image used!\n");
return TPM_DISABLED;
}
static TPM_RESULT vtpmmgr_SaveHashKey(struct tpm_opaque *opq, tpmcmd_t* tpmcmd)
{
TPM_RESULT status = TPM_SUCCESS;
int rc = 0;
size_t bufsize = tpmcmd->req_len - VTPM_COMMAND_HEADER_SIZE;
const void *buf = tpmcmd->req + VTPM_COMMAND_HEADER_SIZE;
if (bufsize < 52) {
vtpmlogerror(VTPM_LOG_VTPM, "VTPM_ORD_SAVEHASHKEY hashkey too short!\n");
status = TPM_BAD_PARAMETER;
goto abort_egress;
}
if (bufsize > 64) {
vtpmlogerror(VTPM_LOG_VTPM, "VTPM_ORD_SAVEHASHKEY hashkey too long!\n");
status = TPM_BAD_PARAMETER;
goto abort_egress;
}
vtpmloginfo(VTPM_LOG_VTPM, "vtpmmgr_SaveHashKey\n");
status = find_vtpm_verified(tpmcmd->domid, opq);
// auto-create vTPMs in group0 when saving a new UUID
// TODO restrict to certain UUIDs (such as all-zero)
// this is not done yet to simplify use of the TPM Manager
if (status == TPM_BAD_PARAMETER) {
opq->group = g_mgr->groups[0].v;
rc = create_vtpm(opq->group, &opq->vtpm, *opq->uuid);
if (rc) {
status = TPM_BAD_PARAMETER;
goto abort_egress;
}
if (opq->group->nr_vtpms == 1)
opq->vtpm->flags = VTPM_FLAG_ADMIN;
printk("SaveHashKey with unknown UUID="UUID_FMT" - creating in auth0 (f=%d)\n",
UUID_BYTES((*opq->uuid)), opq->vtpm->flags);
status = TPM_SUCCESS;
}
if (status)
goto abort_egress;
memcpy(opq->vtpm->data, buf, bufsize);
memset(opq->vtpm->data + bufsize, 0, 64 - bufsize);
vtpm_sync(opq->group, opq->vtpm);
abort_egress:
pack_TPM_RSP_HEADER(tpmcmd->resp, VTPM_TAG_RSP, VTPM_COMMAND_HEADER_SIZE, status);
tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE;
return status;
}
static TPM_RESULT vtpmmgr_LoadHashKey(struct tpm_opaque *opq, tpmcmd_t* tpmcmd)
{
TPM_RESULT status = TPM_SUCCESS;
int i;
uint8_t *buf = tpmcmd->resp + VTPM_COMMAND_HEADER_SIZE;
vtpmloginfo(VTPM_LOG_VTPM, "vtpmmgr_LoadHashKey\n");
tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE;
status = find_vtpm_verified(tpmcmd->domid, opq);
if (status)
goto abort_egress;
memcpy(buf, opq->vtpm->data, 64);
for(i=52; i < 64; i++) {
if (buf[i]) {
tpmcmd->resp_len += 64;
goto abort_egress;
}
}
tpmcmd->resp_len += 52;
abort_egress:
pack_TPM_RSP_HEADER(tpmcmd->resp, VTPM_TAG_RSP, tpmcmd->resp_len, status);
return status;
}
#define CMD_BEGIN \
TPM_RESULT status = TPM_SUCCESS; \
uint32_t in_pos = VTPM_COMMAND_HEADER_SIZE; \
tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE; \
vtpmloginfo(VTPM_LOG_TPM, "%s\n", __func__);
#define CMD_END \
abort_egress: \
if (status) \
tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE; \
pack_TPM_RSP_HEADER(tpmcmd->resp, VTPM_TAG_RSP, tpmcmd->resp_len, status); \
return status
#define UNPACK_IN(type, item...) do { \
status = unpack3_ ## type (tpmcmd->req, &in_pos, tpmcmd->req_len, item); \
if (status) { \
status = TPM_BAD_PARAMETER; \
goto abort_egress; \
} \
} while (0)
#define UNPACK_GROUP(group) do { \
uint32_t group_idx; \
UNPACK_IN(UINT32, &group_idx); \
if (group_idx >= g_mgr->nr_groups) { \
status = TPM_BADINDEX; \
goto abort_egress; \
} \
group = g_mgr->groups[group_idx].v; \
if (!group) { \
status = TPM_AUTHFAIL; \
goto abort_egress; \
} \
} while (0)
#define UNPACK_DONE() do { \
if (in_pos != tpmcmd->req_len) { \
status = TPM_BAD_PARAMETER; \
goto abort_egress; \
} \
} while (0)
#define PACK_OUT(type, item...) do { \
UINT32 isize = sizeof_ ## type(item); \
if (isize + tpmcmd->resp_len > TCPA_MAX_BUFFER_LENGTH) { \
status = TPM_SIZE; \
goto abort_egress; \
} \
pack_ ## type (tpmcmd->resp + tpmcmd->resp_len, item); \
tpmcmd->resp_len += isize; \
} while (0)
#define PACK_BUF ((void*)(tpmcmd->resp + tpmcmd->resp_len))
static TPM_RESULT vtpmmgr_GetBootHash(struct tpm_opaque *opq, tpmcmd_t* tpmcmd)
{
CMD_BEGIN;
UNPACK_DONE();
PACK_OUT(BUFFER, opq->kern_hash, 20);
CMD_END;
}
static TPM_RESULT vtpmmgr_GetQuote(struct tpm_opaque *opq, tpmcmd_t* tpmcmd)
{
CMD_BEGIN;
int i;
void *ibuf;
uint32_t pcr_size;
TPM_PCR_SELECTION sel;
uint32_t extra_info_flags;
UNPACK_IN(VPTR, &ibuf, 20, UNPACK_ALIAS);
UNPACK_IN(TPM_PCR_SELECTION, &sel, UNPACK_ALIAS);
UNPACK_IN(TPM_DEEP_QUOTE_INFO, &extra_info_flags);
UNPACK_DONE();
if (!opq->vtpm) {
status = TPM_BAD_PARAMETER;
goto abort_egress;
}
printk("ibuf: ");
for (i=0; i < 20; i++)
printk("%02x", ((uint8_t*)ibuf)[i]);
printk("\n");
status = vtpm_do_quote(opq->group, *opq->uuid, opq->kern_hash, ibuf, &sel, extra_info_flags, PACK_BUF + 256, &pcr_size, PACK_BUF);
if (status)
goto abort_egress;
tpmcmd->resp_len += 256 + pcr_size;
CMD_END;
}
static TPM_RESULT vtpmmgr_GroupList(tpmcmd_t* tpmcmd)
{
CMD_BEGIN;
UNPACK_DONE();
PACK_OUT(UINT32, g_mgr->nr_groups);
CMD_END;
}
static TPM_RESULT vtpmmgr_GroupNew(tpmcmd_t* tpmcmd)
{
void *privCADigest;
BYTE *pubkey;
struct mem_group *group;
uint32_t group_idx;
CMD_BEGIN;
UNPACK_IN(VPTR, &privCADigest, 20, UNPACK_ALIAS);
UNPACK_IN(PTR, &pubkey, 256, UNPACK_ALIAS);
UNPACK_DONE();
group = vtpm_new_group(privCADigest);
if (!group) {
status = TPM_FAIL;
goto abort_egress;
}
memcpy(group->id_data.saa_pubkey, pubkey, 256);
PACK_OUT(BUFFER, group->id_data.uuid, 16);
PACK_OUT(BUFFER, group->id_data.tpm_aik_public, 256);
PACK_OUT(BUFFER, group->details.recovery_data, 256);
memset(group->details.recovery_data, 0, 256);
group->details.sequence = native_be64(g_mgr->sequence);
if (group != g_mgr->groups[0].v) {
group_idx = g_mgr->nr_groups;
g_mgr->nr_groups++;
g_mgr->groups = realloc(g_mgr->groups, g_mgr->nr_groups*sizeof(struct mem_group_hdr));
memset(&g_mgr->groups[group_idx], 0, sizeof(g_mgr->groups[0]));
g_mgr->groups[group_idx].v = group;
}
vtpm_sync_group(group, SEQ_UPDATE);
CMD_END;
}
static TPM_RESULT vtpmmgr_GroupDel(tpmcmd_t* tpmcmd)
{
CMD_BEGIN;
struct mem_group *group;
uint32_t group_idx, nr_mov;
UNPACK_IN(UINT32, &group_idx);
UNPACK_DONE();
if (group_idx > g_mgr->nr_groups) {
status = TPM_BADINDEX;
goto abort_egress;
}
group = g_mgr->groups[group_idx].v;
if (group) {
int i, j;
for (i = 0; i < group->nr_pages; i++) {
for (j = 0; j < group->data[i].size; j++) {
if (group->data[i].vtpms[j]->flags & VTPM_FLAG_OPEN) {
status = TPM_FAIL;
goto abort_egress;
}
}
}
for (i = 0; i < group->nr_pages; i++) {
for (j = 0; j < group->data[i].size; j++) {
free(group->data[i].vtpms[j]);
}
}
free(group->data);
free(group->seals);
free(group);
}
g_mgr->nr_groups--;
nr_mov = g_mgr->nr_groups - group_idx;
memmove(&g_mgr->groups[group_idx], &g_mgr->groups[group_idx + 1], nr_mov * sizeof(g_mgr->groups[0]));
vtpm_sync_disk(g_mgr, CTR_UPDATE);
CMD_END;
}
static int pack_cfg_list(void* buf, struct mem_group *group)
{
int i;
void *bstart = buf;
memcpy(buf, &group->details.cfg_seq, 8); buf += 8;
buf = pack_UINT32(buf, group->nr_seals);
for(i=0; i < group->nr_seals; i++) {
memcpy(buf, &group->seals[i].digest_release, 20);
buf += 20;
}
memcpy(buf, &group->seal_bits.nr_kerns, 4); buf += 4;
memcpy(buf, &group->seal_bits.kernels, 20 * be32_native(group->seal_bits.nr_kerns));
return buf - bstart + 20 * be32_native(group->seal_bits.nr_kerns);
}
static TPM_RESULT vtpmmgr_GroupShow(tpmcmd_t* tpmcmd)
{
CMD_BEGIN;
struct mem_group *group;
UNPACK_GROUP(group);
UNPACK_DONE();
// TODO show is read-only access, need to hit disk if group is NULL
PACK_OUT(BUFFER, group->id_data.uuid, 16);
PACK_OUT(BUFFER, group->id_data.saa_pubkey, 256);
tpmcmd->resp_len += pack_cfg_list(PACK_BUF, group);
CMD_END;
}
static TPM_RESULT vtpmmgr_GroupActivate(tpmcmd_t* tpmcmd)
{
CMD_BEGIN;
struct mem_group *group;
uint32_t blobSize;
void *blob;
UNPACK_GROUP(group);
UNPACK_IN(UINT32, &blobSize);
UNPACK_IN(VPTR, &blob, blobSize, UNPACK_ALIAS);
UNPACK_DONE();
status = group_do_activate(group, blob, blobSize, tpmcmd->resp, &tpmcmd->resp_len);
CMD_END;
}
/* 2048-bit MODP Group from RFC3526:
* 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }
*/
static uint8_t P_bytes[256] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2,
0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6,
0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11,
0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D,
0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36,
0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F,
0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56,
0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D,
0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08,
0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B,
0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2,
0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9,
0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C,
0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10,
0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
};
static t_uint Gp[] = { 2 };
static void tm_dhkx_gen(void* dhkx1, void* dhkx2, void* out)
{
mpi GX = { 0 }, GY = { 0 }, K = { 0 }, RP = { 0 };
mpi P = { 0 };
t_uint Xp[sizeof(P_bytes) / sizeof(t_uint)];
mpi X = {
.s = 1,
.n = ARRAY_SIZE(Xp),
.p = Xp
};
mpi G = {
.s = 1,
.n = 1,
.p = Gp,
};
mpi_read_binary(&P, P_bytes, sizeof(P_bytes));
do_random(Xp, sizeof(Xp));
mpi_exp_mod(&GX, &G, &X, &P, &RP);
mpi_write_binary(&GX, dhkx2, 256);
mpi_free(&GX);
mpi_read_binary(&GY, dhkx1, 256);
mpi_exp_mod(&K, &GY, &X, &P, &RP);
mpi_free(&P);
mpi_free(&RP);
mpi_free(&GY);
mpi_write_binary(&K, (void*)Xp, 256);
mpi_free(&K);
sha2((void*)Xp, 256, out, 0);
}
static void xor2_256b(void *xv, const void* yv)
{
int i;
uint64_t *x = xv;
const uint64_t *y = yv;
for(i=0; i < 4; i++)
x[i] ^= y[i];
}
static TPM_RESULT vtpmmgr_GroupRegister(tpmcmd_t* tpmcmd)
{
CMD_BEGIN;
struct mem_group *group = NULL;
tpmrsa_context saa_rsa = TPMRSA_CTX_INIT;
struct tpm_authdata digest;
sha1_context ctx;
TPM_PCR_SELECTION sel;
void *dhkx1, *dhkx2, *gk, *sig;
uint32_t extra_info_flags = 0;
UNPACK_GROUP(group);
UNPACK_IN(VPTR, &dhkx1, 256, UNPACK_ALIAS);
UNPACK_IN(VPTR, &sig, 256, UNPACK_ALIAS);
UNPACK_IN(TPM_PCR_SELECTION, &sel, UNPACK_ALIAS);
UNPACK_DONE();
/* Only generating this quote during the same boot that this group was
* created in allows the quote to prove that the group key has never
* been available outside a configuration approved by its SAA.
*/
if (!(group->flags & MEM_GROUP_FLAG_FIRSTBOOT)) {
status = TPM_FAIL;
goto abort_egress;
}
sha1(dhkx1, 256, digest.bits);
tpmrsa_set_pubkey(&saa_rsa, group->id_data.saa_pubkey, 256, 0, 0);
if (tpmrsa_sigcheck(&saa_rsa, sig, digest.bits))
status = TPM_FAIL;
tpmrsa_free(&saa_rsa);
if (status)
goto abort_egress;
dhkx2 = PACK_BUF;
tpmcmd->resp_len += 256;
gk = PACK_BUF;
tpmcmd->resp_len += 32;
tm_dhkx_gen(dhkx1, dhkx2, gk);
xor2_256b(gk, &group->group_key);
sha1_starts(&ctx);
sha1_update(&ctx, (void*)"REGR", 4);
sha1_update(&ctx, dhkx1, 256);
sha1_update(&ctx, dhkx2, 256 + 32);
sha1_finish(&ctx, digest.bits);
status = vtpm_do_quote(group, NULL, NULL, &digest, &sel, extra_info_flags,NULL, NULL, PACK_BUF);
tpmcmd->resp_len += 256;
CMD_END;
}
static TPM_RESULT vtpmmgr_GroupUpdate(tpmcmd_t* tpmcmd)
{
CMD_BEGIN;
struct mem_group *group;
int i;
int hstart;
uint32_t nr_kerns, nr_seals;
uint64_t old_seq, new_seq;
struct mem_seal *seals = NULL;
tpmrsa_context saa_rsa = TPMRSA_CTX_INIT;
unsigned char digest[20];
TPM_RESULT rc;
void *sig, *seal_bits, *kern_bits;
UNPACK_GROUP(group);
UNPACK_IN(VPTR, &sig, 256, UNPACK_ALIAS);
// Hash starts here
hstart = in_pos;
new_seq = be64_native(*(be64_t*)(tpmcmd->req + in_pos));
old_seq = be64_native(group->details.cfg_seq);
in_pos += 8;
if (old_seq > new_seq) {
status = TPM_FAIL;
goto abort_egress;
}
UNPACK_IN(UINT32, &nr_seals);
UNPACK_IN(VPTR, &seal_bits, nr_seals * 20, UNPACK_ALIAS);
UNPACK_IN(UINT32, &nr_kerns);
UNPACK_IN(VPTR, &kern_bits, nr_kerns * 20, UNPACK_ALIAS);
// TODO handle saving larger lists on disk
if (nr_seals > NR_SEALS_PER_GROUP) {
status = TPM_SIZE;
goto abort_egress;
}
if (nr_kerns > NR_KERNS_PER_GROUP) {
status = TPM_SIZE;
goto abort_egress;
}
sha1(tpmcmd->req + hstart, in_pos - hstart, digest);
seals = calloc(nr_seals, sizeof(seals[0]));
for(i=0; i < nr_seals; i++) {
TPM_PCR_SELECTION sel;
UNPACK_IN(TPM_PCR_SELECTION, &sel, UNPACK_ALIAS);
memcpy(&seals[i].digest_release, seal_bits, 20);
seal_bits += 20;
if (sel.sizeOfSelect > 4) {
status = TPM_BAD_PARAMETER;
goto abort_egress;
}
seals[i].pcr_selection = native_le32(0);
memcpy(&seals[i].pcr_selection, sel.pcrSelect, sel.sizeOfSelect);
}
UNPACK_DONE();
tpmrsa_set_pubkey(&saa_rsa, group->id_data.saa_pubkey, 256, 0, 0);
rc = tpmrsa_sigcheck(&saa_rsa, sig, digest);
tpmrsa_free(&saa_rsa);
if (rc) {
printk("sigcheck failed: %d\n", rc);
status = rc;
goto abort_egress;
}
// Commit
free(group->seals);
memcpy(&group->seal_bits.kernels, kern_bits, 20 * nr_kerns);
group->details.cfg_seq = native_be64(new_seq);
group->nr_seals = nr_seals;
group->seals = seals;
group->seal_bits.nr_kerns = native_be32(nr_kerns);
seals = NULL;
group->flags &= ~MEM_GROUP_FLAG_SEAL_VALID;
if (group == g_mgr->groups[0].v)
g_mgr->root_seals_valid = 0;
// TODO use GROUP_KEY_UPDATE or MGR_KEY_UPDATE here?
// only required if this update was to address a potential key leak
vtpm_sync_group(group, SEQ_UPDATE);
abort_egress:
free(seals);
pack_TPM_RSP_HEADER(tpmcmd->resp, VTPM_TAG_RSP, tpmcmd->resp_len, status);
return status;
}
static TPM_RESULT vtpmmgr_VtpmList(tpmcmd_t* tpmcmd)
{
CMD_BEGIN;
struct mem_group *group;
uint32_t vtpm_offset;
int i, j;
UNPACK_GROUP(group);
UNPACK_IN(UINT32, &vtpm_offset);
PACK_OUT(UINT32, group->nr_vtpms);
if (vtpm_offset > group->nr_vtpms)
goto egress;
for(i=0; i < group->nr_pages; i++) {
struct mem_vtpm_page *pg = &group->data[i];
for(j=0; j < pg->size; j++) {
if (vtpm_offset) {
// TODO a proper seek would be far faster
vtpm_offset--;
continue;
}
memcpy(PACK_BUF, pg->vtpms[j]->uuid, 16);
tpmcmd->resp_len += 16;
if (tpmcmd->resp_len + 16 > TCPA_MAX_BUFFER_LENGTH)
goto egress;
}
}
egress:
CMD_END;
}
static TPM_RESULT vtpmmgr_VtpmNew(tpmcmd_t* tpmcmd)
{
CMD_BEGIN;
struct mem_group *group;
struct mem_vtpm *vtpm;
uuid_t newuuid;
int rc;
UNPACK_GROUP(group);
// XXX allow non-random UUIDs for testing
if (tpmcmd->req_len == 14 + 16)
UNPACK_IN(BUFFER, newuuid, 16);
else
gen_random_uuid(newuuid);
UNPACK_DONE();
rc = create_vtpm(group, &vtpm, newuuid);
if (rc) {
status = TPM_FAIL;
goto abort_egress;
}
memset(vtpm->data, 0, 64);
vtpm_sync(group, vtpm);
PACK_OUT(BUFFER, newuuid, 16);
CMD_END;
}
static TPM_RESULT vtpmmgr_VtpmDel(tpmcmd_t* tpmcmd)
{
CMD_BEGIN;
uuid_t uuid;
struct mem_group *group;
struct mem_vtpm *vtpm;
int rc;
UNPACK_IN(BUFFER, uuid, 16);
UNPACK_DONE();
rc = find_vtpm(&group, &vtpm, uuid);
if (rc) {
status = TPM_FAIL;
goto abort_egress;
}
if (vtpm->flags & VTPM_FLAG_OPEN) {
status = TPM_FAIL;
goto abort_egress;
}
delete_vtpm(group, vtpm);
CMD_END;
}
static int vtpmmgr_permcheck(struct tpm_opaque *opq)
{
if (!opq->vtpm)
return 1;
if (opq->vtpm->flags & VTPM_FLAG_ADMIN)
return 0;
return 1;
}
TPM_RESULT vtpmmgr_handle_getrandom(struct tpm_opaque *opaque,
tpmcmd_t* tpmcmd)
{
TPM_RESULT status = TPM_SUCCESS;
TPM_TAG tag;
UINT32 size;
const int max_rand_size = TCPA_MAX_BUFFER_LENGTH -
sizeof_TPM_RQU_GetRandom(tpmcmd->req);
UINT32 rand_offset;
UINT32 rand_size;
TPM_COMMAND_CODE ord;
BYTE *p;
if (tpmcmd->req_len != sizeof_TPM_RQU_GetRandom(tpmcmd->req)) {
status = TPM_BAD_PARAMETER;
tag = TPM_TAG_RQU_COMMAND;
goto abort_egress;
}
p = unpack_TPM_RQU_HEADER(tpmcmd->req, &tag, &size, &ord);
if (!hw_is_tpm2()) {
size = TCPA_MAX_BUFFER_LENGTH;
TPMTRYRETURN(TPM_TransmitData(tpmcmd->req, tpmcmd->req_len,
tpmcmd->resp, &size));
tpmcmd->resp_len = size;
return TPM_SUCCESS;
}
/* TPM_GetRandom req: <header><uint32 num bytes> */
unpack_UINT32(p, &rand_size);
/* Returning fewer bytes is acceptable per the spec. */
if (rand_size > max_rand_size)
rand_size = max_rand_size;
/* Call TPM2_GetRandom but return a TPM_GetRandom response. */
/* TPM_GetRandom resp: <header><uint32 num bytes><num random bytes> */
rand_offset = sizeof_TPM_RSP_HEADER(tpmcmd->resp) +
sizeof_UINT32(tpmcmd->resp);
TPMTRYRETURN(TPM2_GetRandom(&rand_size, tpmcmd->resp + rand_offset));
p = pack_TPM_RSP_HEADER(tpmcmd->resp, TPM_TAG_RSP_COMMAND,
rand_offset + rand_size, status);
p = pack_UINT32(p, rand_size);
tpmcmd->resp_len = rand_offset + rand_size;
return status;
abort_egress:
tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE;
pack_TPM_RSP_HEADER(tpmcmd->resp, tag + 3, tpmcmd->resp_len, status);
return status;
}
TPM_RESULT vtpmmgr_handle_cmd(
struct tpm_opaque *opaque,
tpmcmd_t* tpmcmd)
{
TPM_RESULT status = TPM_SUCCESS;
TPM_TAG tag;
UINT32 size;
TPM_COMMAND_CODE ord;
if (tpmcmd->req_len < sizeof_TPM_RQU_HEADER(tpmcmd->req)) {
status = TPM_BAD_PARAMETER;
tag = TPM_TAG_RQU_COMMAND;
goto abort_egress;
}
unpack_TPM_RQU_HEADER(tpmcmd->req,
&tag, &size, &ord);
/* Handle the command now */
switch(tag) {
case VTPM_TAG_REQ:
// This is a vTPM command
switch(ord) {
case VTPM_ORD_SAVEHASHKEY:
return vtpmmgr_SaveHashKey(opaque, tpmcmd);
case VTPM_ORD_LOADHASHKEY:
return vtpmmgr_LoadHashKey(opaque, tpmcmd);
case VTPM_ORD_GET_BOOT_HASH:
return vtpmmgr_GetBootHash(opaque, tpmcmd);
case VTPM_ORD_GET_QUOTE:
return vtpmmgr_GetQuote(opaque, tpmcmd);
default:
vtpmlogerror(VTPM_LOG_VTPM, "Invalid vTPM Ordinal %" PRIu32 "\n", ord);
status = TPM_BAD_ORDINAL;
}
break;
case VTPM_TAG_REQ2:
// This is a management command
if (vtpmmgr_permcheck(opaque)) {
status = TPM_AUTHFAIL;
vtpmlogerror(VTPM_LOG_VTPM, "Rejected attempt to use management command from client\n");
break;
}
switch (ord) {
case VTPM_ORD_GROUP_LIST:
return vtpmmgr_GroupList(tpmcmd);
case VTPM_ORD_GROUP_NEW:
return vtpmmgr_GroupNew(tpmcmd);
case VTPM_ORD_GROUP_DEL:
return vtpmmgr_GroupDel(tpmcmd);
case VTPM_ORD_GROUP_ACTIVATE:
return vtpmmgr_GroupActivate(tpmcmd);
case VTPM_ORD_GROUP_REGISTER:
return vtpmmgr_GroupRegister(tpmcmd);
case VTPM_ORD_GROUP_UPDATE:
return vtpmmgr_GroupUpdate(tpmcmd);
case VTPM_ORD_GROUP_SHOW:
return vtpmmgr_GroupShow(tpmcmd);
case VTPM_ORD_VTPM_LIST:
return vtpmmgr_VtpmList(tpmcmd);
case VTPM_ORD_VTPM_NEW:
return vtpmmgr_VtpmNew(tpmcmd);
case VTPM_ORD_VTPM_DEL:
return vtpmmgr_VtpmDel(tpmcmd);
default:
vtpmlogerror(VTPM_LOG_VTPM, "Invalid TM Ordinal %" PRIu32 "\n", ord);
status = TPM_BAD_ORDINAL;
}
break;
case TPM_TAG_RQU_COMMAND:
case TPM_TAG_RQU_AUTH1_COMMAND:
case TPM_TAG_RQU_AUTH2_COMMAND:
//This is a TPM passthrough command
switch(ord) {
case TPM_ORD_GetRandom:
vtpmloginfo(VTPM_LOG_VTPM, "Passthrough: TPM_GetRandom\n");
return vtpmmgr_handle_getrandom(opaque, tpmcmd);
case TPM_ORD_PcrRead:
vtpmloginfo(VTPM_LOG_VTPM, "Passthrough: TPM_PcrRead\n");
// Quotes also need to be restricted to hide PCR values
break;
case TPM_ORD_Extend:
// TODO allow to certain clients? A malicious client
// could scramble PCRs and make future quotes invalid.
if (vtpmmgr_permcheck(opaque)) {
vtpmlogerror(VTPM_LOG_VTPM, "Disallowed TPM_Extend\n");
status = TPM_DISABLED_CMD;
goto abort_egress;
} else {
vtpmloginfo(VTPM_LOG_VTPM, "Passthrough: TPM_Extend\n");
}
break;
default:
vtpmlogerror(VTPM_LOG_VTPM, "TPM Disallowed Passthrough ord=%" PRIu32 "\n", ord);
status = TPM_DISABLED_CMD;
goto abort_egress;
}
size = TCPA_MAX_BUFFER_LENGTH;
TPMTRYRETURN(TPM_TransmitData(tpmcmd->req, tpmcmd->req_len, tpmcmd->resp, &size));
tpmcmd->resp_len = size;
return TPM_SUCCESS;
default:
vtpmlogerror(VTPM_LOG_VTPM, "Invalid tag=%" PRIu16 "\n", tag);
status = TPM_BADTAG;
}
abort_egress:
tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE;
pack_TPM_RSP_HEADER(tpmcmd->resp, tag + 3, tpmcmd->resp_len, status);
return status;
}