fwtpm: scope context replay to sessions and accept unsalted param-enc

pull/518/head
aidan garske 2026-06-03 10:57:13 -07:00
parent 61f7fec929
commit 5699061b53
2 changed files with 60 additions and 45 deletions

View File

@ -3042,19 +3042,22 @@ static TPM_RC FwCmd_ContextSave(FWTPM_CTX* ctx, TPM2_Packet* cmd,
}
}
if (rc == 0 &&
/* Only session contexts carry single-use replay tracking (the policy
* replay concern); object contexts are freely reloadable, so tracking
* them would exhaust the small live set under normal multi-load use. */
if (rc == 0 && isSession &&
ctx->contextLiveCount >= (int)(sizeof(ctx->contextLive) /
sizeof(ctx->contextLive[0]))) {
/* No room to record another live context; emitting a blob we cannot
* track would have it rejected as non-live at load time. */
rc = TPM_RC_OBJECT_MEMORY;
}
if (rc == 0) {
/* TPMS_CONTEXT: sequence(8) | savedHandle(4) | hierarchy(4) | blob */
ctx->contextSeqCounter++;
/* Record this context as live so it loads at most once, in any order */
ctx->contextLive[ctx->contextLiveCount++] = ctx->contextSeqCounter;
if (isSession) {
/* Record so this session context loads at most once, any order */
ctx->contextLive[ctx->contextLiveCount++] = ctx->contextSeqCounter;
}
seqHi = (UINT32)(ctx->contextSeqCounter >> 32);
seqLo = (UINT32)(ctx->contextSeqCounter & 0xFFFFFFFFu);
TPM2_Packet_AppendU32(rsp, seqHi);
@ -3137,10 +3140,13 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd,
TPM2_Packet_ParseU16(cmd, &blobSz);
}
/* Replay protection: the context must be a live (saved, not-yet-loaded)
* sequence. A load consumes it; independent saved contexts may load in
* any order. */
if (rc == 0) {
/* Replay protection applies to session contexts only: a saved session
* must be a live (not-yet-loaded) sequence and a load consumes it, so a
* satisfied policy session cannot be replayed. Object contexts are
* reloadable any number of times and are not tracked. */
if (rc == 0 &&
((savedHandle & 0xFF000000) == HMAC_SESSION_FIRST ||
(savedHandle & 0xFF000000) == POLICY_SESSION_FIRST)) {
loadSeq = ((UINT64)seqHi << 32) | (UINT64)seqLo;
for (liveScan = 0; liveScan < ctx->contextLiveCount; liveScan++) {
if (ctx->contextLive[liveScan] == loadSeq) {
@ -15793,14 +15799,13 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx,
}
#ifndef FWTPM_NO_PARAM_ENC
/* Detect encryption session (first non-PW with
* symmetric alg). A session with an empty
* sessionKey (unsalted and unbound) derives a
* wire-observable key, so it must not be used
* for parameter encryption. */
/* Detect encryption session (first non-PW with a
* symmetric alg). Unsalted/unbound sessions are
* accepted for client compatibility; over the
* loopback transport their key is only observable
* to a local peer. */
if (encSess == NULL &&
sess->symmetric.algorithm != TPM_ALG_NULL &&
sess->sessionKey.size > 0) {
sess->symmetric.algorithm != TPM_ALG_NULL) {
encSess = sess;
/* decrypt attr = client encrypted cmd param */
if ((attribs & TPMA_SESSION_decrypt)

View File

@ -8672,10 +8672,10 @@ static void test_fwtpm_context_save(void)
static void test_fwtpm_context_load_replay_rejected(void)
{
FWTPM_CTX ctx;
int rspSize = 0, ctxSzA, ctxSzB, pos;
UINT32 keyH;
byte savedCtxA[512];
byte savedCtxB[512];
int rspSize = 0, ctxSz, pos;
UINT32 keyH, sessH;
byte savedObj[MAX_CONTEXT_SIZE];
byte savedSess[MAX_CONTEXT_SIZE];
memset(&ctx, 0, sizeof(ctx));
AssertIntEQ(fwtpm_test_startup(&ctx), 0);
@ -8686,50 +8686,60 @@ static void test_fwtpm_context_load_replay_rejected(void)
#endif
AssertIntNE(keyH, 0);
/* Save context A, then save context B while A is still outstanding */
/* Object contexts are freely reloadable (real TPM behavior, relied on by
* tpm2-tools): save once, load the same blob twice. */
BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_ContextSave);
PutU32BE(gCmd + 10, keyH);
FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
ctxSzA = rspSize - TPM2_HEADER_SIZE;
AssertIntGT(ctxSzA, 0);
memcpy(savedCtxA, gRsp + TPM2_HEADER_SIZE, ctxSzA);
ctxSz = rspSize - TPM2_HEADER_SIZE;
AssertIntGT(ctxSz, 0);
memcpy(savedObj, gRsp + TPM2_HEADER_SIZE, ctxSz);
BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_ContextSave);
PutU32BE(gCmd + 10, keyH);
rspSize = 0;
FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
ctxSzB = rspSize - TPM2_HEADER_SIZE;
memcpy(savedCtxB, gRsp + TPM2_HEADER_SIZE, ctxSzB);
/* Load A first (older blob, B still outstanding) — must succeed */
pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad);
memcpy(gCmd + pos, savedCtxA, ctxSzA); pos += ctxSzA;
memcpy(gCmd + pos, savedObj, ctxSz); pos += ctxSz;
PutU32BE(gCmd + 2, (UINT32)pos);
rspSize = 0;
FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
/* Replaying A is rejected (single-use) */
pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad);
memcpy(gCmd + pos, savedCtxA, ctxSzA); pos += ctxSzA;
memcpy(gCmd + pos, savedObj, ctxSz); pos += ctxSz;
PutU32BE(gCmd + 2, (UINT32)pos);
rspSize = 0;
FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
/* Session contexts are single-use: a satisfied policy/HMAC session must
* not be replayable. Save a session, load once, then reject the replay. */
sessH = StartSessionHelper(&ctx, TPM_SE_HMAC);
AssertIntNE(sessH, 0);
BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_ContextSave);
PutU32BE(gCmd + 10, sessH);
rspSize = 0;
FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
ctxSz = rspSize - TPM2_HEADER_SIZE;
AssertIntGT(ctxSz, 0);
memcpy(savedSess, gRsp + TPM2_HEADER_SIZE, ctxSz);
pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad);
memcpy(gCmd + pos, savedSess, ctxSz); pos += ctxSz;
PutU32BE(gCmd + 2, (UINT32)pos);
rspSize = 0;
FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad);
memcpy(gCmd + pos, savedSess, ctxSz); pos += ctxSz;
PutU32BE(gCmd + 2, (UINT32)pos);
rspSize = 0;
FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0);
AssertIntNE(GetRspRC(gRsp), TPM_RC_SUCCESS);
/* B is still loadable after A was consumed (any-order multi-context) */
pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad);
memcpy(gCmd + pos, savedCtxB, ctxSzB); pos += ctxSzB;
PutU32BE(gCmd + 2, (UINT32)pos);
rspSize = 0;
FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
FlushHandle(&ctx, keyH);
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tContextLoad replay + multi-context:\tPassed\n");
printf("Test fwTPM:\tContextLoad object reload + session replay:\tPassed\n");
}
/* When the command client changes, transient objects must be flushed so a