F-12709 - Order handle capability pages numerically

pull/588/head
Aidan Garske 2026-08-31 08:58:52 -07:00
parent 657cfa499f
commit 17c6926f80
2 changed files with 255 additions and 100 deletions

View File

@ -1207,6 +1207,76 @@ static void FwPatchMoreData(TPM2_Packet* rsp, int pos, byte v)
rsp->buf[pos] = v;
}
/* Select the numerically lowest handle in handleClass that is at or above
* property and, after the first selection, greater than previous. The slot
* tables are intentionally rescanned per result: their configured defaults
* are small, and this avoids scratch storage proportional to the table sizes. */
static int FwSelectCapabilityHandle(const FWTPM_CTX* ctx, UINT32 handleClass,
UINT32 property, UINT32 previous, int havePrevious, UINT32* selected)
{
int idx;
int slotCount = 0;
int found = 0;
int used;
UINT32 candidate;
if (handleClass == HR_TRANSIENT) {
slotCount = FWTPM_MAX_OBJECTS;
}
else if (handleClass == HR_PERSISTENT) {
slotCount = FWTPM_MAX_PERSISTENT;
}
#ifndef FWTPM_NO_NV
else if (handleClass == HR_NV_INDEX) {
slotCount = FWTPM_MAX_NV_INDICES;
}
#endif
else if (handleClass == HR_HMAC_SESSION ||
handleClass == HR_POLICY_SESSION) {
slotCount = FWTPM_MAX_SESSIONS;
}
for (idx = 0; idx < slotCount; idx++) {
used = 0;
candidate = 0;
if (handleClass == HR_TRANSIENT) {
used = ctx->objects[idx].used;
candidate = ctx->objects[idx].handle;
}
else if (handleClass == HR_PERSISTENT) {
used = ctx->persistent[idx].used;
candidate = ctx->persistent[idx].handle;
}
#ifndef FWTPM_NO_NV
else if (handleClass == HR_NV_INDEX) {
used = ctx->nvIndices[idx].inUse;
candidate = ctx->nvIndices[idx].nvPublic.nvIndex;
}
#endif
else if (handleClass == HR_HMAC_SESSION ||
handleClass == HR_POLICY_SESSION) {
used = ctx->sessions[idx].used;
candidate = ctx->sessions[idx].handle;
}
/* TPM_HT_LOADED_SESSION (0x02) covers both HMAC and policy sessions.
* Preserve each real 0x02/0x03 handle prefix so the reported handle
* remains directly usable instead of normalizing it to 0x02. A 0x03
* query is deliberately limited to loaded policy sessions because
* fwTPM has no saved-session list. */
if (used && (handleClass == HR_HMAC_SESSION ||
(candidate & HR_RANGE_MASK) == handleClass) &&
candidate >= property &&
(!havePrevious || candidate > previous) &&
(!found || candidate < *selected)) {
*selected = candidate;
found = 1;
}
}
return found;
}
/* --- TPM2_GetCapability (CC 0x017A) --- */
static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd,
int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag)
@ -1219,8 +1289,6 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd,
int paramSzPos, paramStart;
int moreDataPos;
(void)ctx;
if (cmdSize < TPM2_HEADER_SIZE + 12) {
rc = TPM_RC_COMMAND_SIZE;
}
@ -1694,107 +1762,30 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd,
}
case TPM_CAP_HANDLES: {
int count = 0;
int idx;
UINT32 handleClass = property & 0xFF000000;
UINT32 handleClass = property & HR_RANGE_MASK;
UINT32 selected = 0;
UINT32 previous = 0;
int countPos = rsp->pos;
int emitted = 0;
int havePrevious = 0;
/* Filter by handle class per TPM 2.0 spec Part 2 Section 8.4:
* only return handles whose upper byte matches property */
if (handleClass == 0x80000000) {
/* Transient objects */
for (idx = 0; idx < FWTPM_MAX_OBJECTS; idx++) {
if (ctx->objects[idx].used &&
ctx->objects[idx].handle >= property) {
count++;
}
}
TPM2_Packet_AppendU32(rsp, 0); /* back-patched below */
while ((UINT32)emitted < propertyCount &&
FwSelectCapabilityHandle(ctx, handleClass, property,
previous, havePrevious, &selected)) {
TPM2_Packet_AppendU32(rsp, selected);
previous = selected;
havePrevious = 1;
emitted++;
}
else if (handleClass == 0x81000000) {
/* Persistent objects */
for (idx = 0; idx < FWTPM_MAX_PERSISTENT; idx++) {
if (ctx->persistent[idx].used &&
ctx->persistent[idx].handle >= property) {
count++;
}
}
}
#ifndef FWTPM_NO_NV
else if (handleClass == 0x01000000) {
/* NV indices */
for (idx = 0; idx < FWTPM_MAX_NV_INDICES; idx++) {
if (ctx->nvIndices[idx].inUse &&
ctx->nvIndices[idx].nvPublic.nvIndex >= property) {
count++;
}
}
}
#endif
else if (handleClass == 0x02000000 ||
handleClass == 0x03000000) {
/* HMAC / policy sessions */
for (idx = 0; idx < FWTPM_MAX_SESSIONS; idx++) {
if (ctx->sessions[idx].used &&
ctx->sessions[idx].handle >= property) {
count++;
}
}
}
/* Other classes (PCR, permanent): report 0 */
FwPatchU32BE(rsp, countPos, (UINT32)emitted);
if ((UINT32)count > propertyCount) {
count = (int)propertyCount;
FwPatchMoreData(rsp, moreDataPos, 1); /* more handles available */
}
TPM2_Packet_AppendU32(rsp, (UINT32)count);
if (count > 0) {
int emitted = 0;
if (handleClass == 0x81000000) {
for (idx = 0; idx < FWTPM_MAX_PERSISTENT &&
emitted < count; idx++) {
if (ctx->persistent[idx].used &&
ctx->persistent[idx].handle >= property) {
TPM2_Packet_AppendU32(rsp,
ctx->persistent[idx].handle);
emitted++;
}
}
}
else if (handleClass == 0x80000000) {
for (idx = 0; idx < FWTPM_MAX_OBJECTS &&
emitted < count; idx++) {
if (ctx->objects[idx].used &&
ctx->objects[idx].handle >= property) {
TPM2_Packet_AppendU32(rsp,
ctx->objects[idx].handle);
emitted++;
}
}
}
#ifndef FWTPM_NO_NV
else if (handleClass == 0x01000000) {
for (idx = 0; idx < FWTPM_MAX_NV_INDICES &&
emitted < count; idx++) {
if (ctx->nvIndices[idx].inUse &&
ctx->nvIndices[idx].nvPublic.nvIndex >= property) {
TPM2_Packet_AppendU32(rsp,
ctx->nvIndices[idx].nvPublic.nvIndex);
emitted++;
}
}
}
#endif
else if (handleClass == 0x02000000 ||
handleClass == 0x03000000) {
for (idx = 0; idx < FWTPM_MAX_SESSIONS &&
emitted < count; idx++) {
if (ctx->sessions[idx].used &&
ctx->sessions[idx].handle >= property) {
TPM2_Packet_AppendU32(rsp,
ctx->sessions[idx].handle);
emitted++;
}
}
}
/* If selection exhausted the table before reaching the requested
* count, it already proved there is no next page. */
if ((UINT32)emitted == propertyCount &&
FwSelectCapabilityHandle(ctx, handleClass, property,
previous, havePrevious, &selected)) {
FwPatchMoreData(rsp, moreDataPos, 1);
}
break;
}

View File

@ -1161,6 +1161,165 @@ static void test_fwtpm_getcap_paging(void)
fwtpm_pass("GetCapability paging convergence:", 0);
}
#if FWTPM_MAX_OBJECTS >= 2 || FWTPM_MAX_PERSISTENT >= 2 || \
FWTPM_MAX_SESSIONS >= 2 || \
(!defined(FWTPM_NO_NV) && FWTPM_MAX_NV_INDICES >= 2)
static UINT32 getcap_handle_page_ex(FWTPM_CTX* ctx, UINT32 property,
UINT32 propertyCount, byte* moreData, UINT32* handleCount)
{
int rc, rspSize, cmdSz;
cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0,
TPM_CC_GetCapability);
PutU32BE(gCmd + cmdSz, TPM_CAP_HANDLES); cmdSz += 4;
PutU32BE(gCmd + cmdSz, property); cmdSz += 4;
PutU32BE(gCmd + cmdSz, propertyCount); cmdSz += 4;
PutU32BE(gCmd + 2, (UINT32)cmdSz);
rspSize = 0;
rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0);
AssertIntEQ(rc, TPM_RC_SUCCESS);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
AssertTrue(rspSize >= TPM2_HEADER_SIZE + 9);
AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 1),
(int)TPM_CAP_HANDLES);
*moreData = gRsp[TPM2_HEADER_SIZE];
*handleCount = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5);
AssertTrue(*handleCount <= propertyCount);
if (*handleCount > 0U) {
AssertTrue(rspSize >= TPM2_HEADER_SIZE + 13);
return GetU32BE(gRsp + TPM2_HEADER_SIZE + 9);
}
return 0;
}
static UINT32 getcap_handle_page(FWTPM_CTX* ctx, UINT32 property,
byte* moreData)
{
UINT32 handleCount;
UINT32 handle;
handle = getcap_handle_page_ex(ctx, property, 1, moreData,
&handleCount);
AssertIntEQ(handleCount, 1);
return handle;
}
static void check_handle_zero_count(FWTPM_CTX* ctx, UINT32 property)
{
UINT32 handleCount;
byte moreData;
(void)getcap_handle_page_ex(ctx, property, 0, &moreData, &handleCount);
AssertIntEQ(handleCount, 0);
AssertIntEQ(moreData, 1);
}
static void check_empty_handle_class(FWTPM_CTX* ctx, UINT32 property)
{
UINT32 handleCount;
byte moreData;
(void)getcap_handle_page_ex(ctx, property, 1, &moreData, &handleCount);
AssertIntEQ(handleCount, 0);
AssertIntEQ(moreData, 0);
}
static void check_handle_paging(FWTPM_CTX* ctx, UINT32 firstProperty,
UINT32 lowHandle, UINT32 highHandle)
{
UINT32 handle;
byte moreData;
handle = getcap_handle_page(ctx, firstProperty, &moreData);
AssertIntEQ(handle, lowHandle);
AssertIntEQ(moreData, 1);
handle = getcap_handle_page(ctx, handle + 1, &moreData);
AssertIntEQ(handle, highHandle);
AssertIntEQ(moreData, 0);
}
/* Handle capability pages are numerically ordered independently of their
* backing-slot order, so cursor-based enumeration cannot omit an entry. */
static void test_fwtpm_getcap_handles_ordered(void)
{
FWTPM_CTX ctx;
UINT32 lowHandle;
UINT32 highHandle;
#if FWTPM_MAX_SESSIONS >= 2
byte moreData;
#endif
(void)remove(FWTPM_NV_FILE);
XMEMSET(&ctx, 0, sizeof(ctx));
AssertIntEQ(fwtpm_test_startup(&ctx), 0);
/* PCR and permanent handles have no backing capability table. */
check_empty_handle_class(&ctx, PCR_FIRST);
check_empty_handle_class(&ctx, PERMANENT_FIRST);
#if FWTPM_MAX_OBJECTS >= 2
lowHandle = TRANSIENT_FIRST + 0x10u;
highHandle = TRANSIENT_FIRST + 0x20u;
ctx.objects[0].used = 1;
ctx.objects[0].handle = highHandle;
ctx.objects[1].used = 1;
ctx.objects[1].handle = lowHandle;
check_handle_zero_count(&ctx, TRANSIENT_FIRST);
check_handle_paging(&ctx, TRANSIENT_FIRST, lowHandle, highHandle);
XMEMSET(ctx.objects, 0, sizeof(ctx.objects));
#endif /* FWTPM_MAX_OBJECTS >= 2 */
#if FWTPM_MAX_PERSISTENT >= 2
lowHandle = PERSISTENT_FIRST + 0x10u;
highHandle = PERSISTENT_FIRST + 0x20u;
ctx.persistent[0].used = 1;
ctx.persistent[0].handle = highHandle;
ctx.persistent[1].used = 1;
ctx.persistent[1].handle = lowHandle;
check_handle_zero_count(&ctx, PERSISTENT_FIRST);
check_handle_paging(&ctx, PERSISTENT_FIRST, lowHandle, highHandle);
XMEMSET(ctx.persistent, 0, sizeof(ctx.persistent));
#endif /* FWTPM_MAX_PERSISTENT >= 2 */
#if !defined(FWTPM_NO_NV) && FWTPM_MAX_NV_INDICES >= 2
lowHandle = NV_INDEX_FIRST + 0x10u;
highHandle = NV_INDEX_FIRST + 0x20u;
ctx.nvIndices[0].inUse = 1;
ctx.nvIndices[0].nvPublic.nvIndex = highHandle;
ctx.nvIndices[1].inUse = 1;
ctx.nvIndices[1].nvPublic.nvIndex = lowHandle;
check_handle_zero_count(&ctx, NV_INDEX_FIRST);
check_handle_paging(&ctx, NV_INDEX_FIRST, lowHandle, highHandle);
XMEMSET(ctx.nvIndices, 0, sizeof(ctx.nvIndices));
#endif /* !FWTPM_NO_NV && FWTPM_MAX_NV_INDICES >= 2 */
#if FWTPM_MAX_SESSIONS >= 2
/* A loaded-session query intentionally spans HMAC and policy sessions;
* fwTPM reports each session's real, directly usable handle prefix. */
lowHandle = HMAC_SESSION_FIRST + 0x10u;
highHandle = POLICY_SESSION_FIRST + 0x20u;
ctx.sessions[0].used = 1;
ctx.sessions[0].handle = highHandle;
ctx.sessions[1].used = 1;
ctx.sessions[1].handle = lowHandle;
check_handle_zero_count(&ctx, HMAC_SESSION_FIRST);
check_handle_paging(&ctx, HMAC_SESSION_FIRST, lowHandle, highHandle);
/* A policy-session query must exclude the lower HMAC session and return
* the policy handle without claiming another page. */
highHandle = getcap_handle_page(&ctx, POLICY_SESSION_FIRST, &moreData);
AssertIntEQ(highHandle, POLICY_SESSION_FIRST + 0x20u);
AssertIntEQ(moreData, 0);
XMEMSET(ctx.sessions, 0, sizeof(ctx.sessions));
#endif /* FWTPM_MAX_SESSIONS >= 2 */
FWTPM_Cleanup(&ctx);
(void)remove(FWTPM_NV_FILE);
fwtpm_pass("GetCapability(HANDLES) ordered paging:", 0);
}
#endif /* handle capability test has at least two slots */
/* ================================================================== */
/* Command-group gates (FWTPM_NO_* macros) */
/* ================================================================== */
@ -13328,6 +13487,11 @@ int fwtpm_unit_tests(int argc, char *argv[])
test_fwtpm_getcap_properties();
test_fwtpm_getcap_pcrs();
test_fwtpm_getcap_paging();
#if FWTPM_MAX_OBJECTS >= 2 || FWTPM_MAX_PERSISTENT >= 2 || \
FWTPM_MAX_SESSIONS >= 2 || \
(!defined(FWTPM_NO_NV) && FWTPM_MAX_NV_INDICES >= 2)
test_fwtpm_getcap_handles_ordered();
#endif /* handle capability test has at least two slots */
test_fwtpm_total_commands();
/* Command-group gates (FWTPM_NO_* macros) */