/// <summary> /// Dispatch a command to the underlying TPM. This method implements all /// significant functionality. It examines the command stream and performs /// (approximately) the following actions: /// 1) If the command references a handle (session or transient object), then /// TBS makes sure that the entity is loaded. If it is, then the handle is /// "translated" to the underlying TPM handle. If it is not, then TBS checks /// to see if it has a saved context for the entity, and if so, loads it. /// 2) If the command will fill a slot, then TBS ensures that a slot is available. /// It does this by ContextSaving the LRU entity of the proper type (that is /// not used in this command). /// </summary> /// <param name="caller"></param> /// <param name="active"></param> /// <param name="inBuf"></param> /// <param name="outBuf"></param> /// <exception cref="Exception"></exception> internal void DispatchCommand(TbsContext caller, CommandModifier active, byte[] inBuf, out byte[] outBuf) { lock (this) { CommandNumber++; // ReSharper disable once CompareOfFloatsByEqualityOperator if (StateSaveProbability != 0.0) { // S3 debug support DebugStateSave(); LastStateSaveCommandNumber = CommandNumber; } CommandHeader commandHeader; TpmHandle[] inHandles; SessionIn[] inSessions; byte[] commandParmsNoHandles; bool legalCommand = CommandProcessor.CrackCommand(inBuf, out commandHeader, out inHandles, out inSessions, out commandParmsNoHandles); if (!legalCommand) { // Is a diagnostics command. Pass through to TPM (a real RM would refuse). TpmDevice.DispatchCommand(active, inBuf, out outBuf); return; } TpmCc cc = commandHeader.CommandCode; // Lookup command CommandInfo command = Tpm2.CommandInfoFromCommandCode(cc); if (command == null) { throw new Exception("Unrecognized command"); } if (cc == TpmCc.ContextLoad || cc == TpmCc.ContextSave) { Debug.WriteLine("ContextLoad and ContextSave are not supported in this build"); outBuf = Marshaller.GetTpmRepresentation(new Object[] { TpmSt.NoSessions, (uint)10, TpmRc.NotUsed }); } // Look up referenced objects and sessions ObjectContext[] neededObjects = GetReferencedObjects(caller, inHandles); ObjectContext[] neededSessions = GetSessions(caller, inSessions); ObjectContext[] neededEntities = neededObjects != null ? neededSessions != null ? neededObjects.Concat(neededSessions).ToArray() : neededObjects : neededSessions; #if false // LibTester may intentionally use invalid handles, therefore it always // work in the passthru mode (all correctness checks by TSS infra suppressed) if (!Tpm2._TssBehavior.Passthrough && (neededObjects == null || neededSessions == null)) #endif if (neededObjects == null || neededSessions == null) { // One or more of the handles was not registered for the context byte[] ret = FormatError(TpmRc.Handle); outBuf = ret; return; } // Load referenced objects and sessions (free slots if needed) // It's important to load all object and session handles in a single call // to LoadEntities(), as for some commands (e.g. GetSessionAuditDigest) // the objects array may contain session handles. In this case the session // handles loaded by the invocation of LoadEntities for neededObjects // may be evicted again during the subsequent call for neededSessions. var expectedResponses = Tpm._GetExpectedResponses(); if (!LoadEntities(neededEntities)) { throw new Exception("Failed to make space for objects or sessions"); } else { // At this point everything referenced should be loaded, and // there will be a free slot if needed so we can translate // the input handles to the underlying handles ReplaceHandlesIn(inHandles, inSessions, neededObjects, neededSessions); } // Re-create the command using translated object and session handles byte[] commandBuf = CommandProcessor.CreateCommand(commandHeader.CommandCode, inHandles, inSessions, commandParmsNoHandles); if (!Tpm2._TssBehavior.Passthrough) { Debug.Assert(commandBuf.Length == inBuf.Length); } byte[] responseBuf; // TODO: Virtualize TPM2_GetCapability() for handle enumeration. // // Execute command on underlying TPM device. // If we get an ObjectMemory or SessionMemory error we try to make more space and try again // Note: If the TPM device throws an error above we let it propagate out. There should be no side // effects on TPM state that the TBS cares about. // ulong firstCtxSeqNum = 0; while (true) { Tpm._ExpectResponses(expectedResponses); TpmDevice.DispatchCommand(active, commandBuf, out responseBuf); TpmRc res = GetResultCode(responseBuf); if (res == TpmRc.Success || expectedResponses != null && expectedResponses.Contains(res)) { break; } if (res == TpmRc.ContextGap) { ulong seqNum = ShortenSessionContextGap(firstCtxSeqNum); if (seqNum == 0) { break; // Failed to handle CONTEXT_GAP error } if (firstCtxSeqNum == 0) { firstCtxSeqNum = seqNum; } //if (firstCtxSeqNum != 0) // Console.WriteLine("DispatchCommand: CONTEXT_GAP handled"); continue; } var slotType = SlotType.NoSlot; if (res == TpmRc.ObjectHandles || res == TpmRc.ObjectMemory) { slotType = SlotType.ObjectSlot; } else if (res == TpmRc.SessionHandles || res == TpmRc.SessionMemory) { slotType = SlotType.SessionSlot; } else { // Command failure not related to resources break; } if (!MakeSpace(slotType, neededEntities)) { // Failed to make an object slot in the TPM responseBuf = TpmErrorHelpers.BuildErrorResponseBuffer(TpmRc.Memory); break; } } // Parse the response from the TPM TpmSt responseTag; uint responseParamSize; TpmRc resultCode; TpmHandle[] responseHandles; SessionOut[] responseSessions; byte[] responseParmsNoHandles, responseParmsWithHandles; CommandProcessor.SplitResponse(responseBuf, command.HandleCountOut, out responseTag, out responseParamSize, out resultCode, out responseHandles, out responseSessions, out responseParmsNoHandles, out responseParmsWithHandles); // In case of an error there is no impact on the loaded sessions, but // we update the LRU values because the user will likely try again. if (resultCode != TpmRc.Success) { outBuf = responseBuf; UpdateLastUseCount(new[] { neededObjects, neededSessions }); return; } // Update TBS database with any newly created TPM objects ProcessUpdatedTpmState(caller, command, responseHandles, neededObjects); // And if there were any newly created objects use the new DB entries // to translate the handles ReplaceHandlesOut(responseHandles); outBuf = CommandProcessor.CreateResponse(resultCode, responseHandles, responseSessions, responseParmsNoHandles); Debug.Assert(outBuf.Length == responseBuf.Length); } // lock(this) }
/// <summary> /// Dispatch a command to the underlying TPM. This method implements all significant functionality. /// DispatchCommand examines the command stream and performs (approximately) the following functions /// 1) If the command references a handle (session or transient object) then TBS makes sure that the entity /// is loaded. If it is, then the handle is "translated" to the underlying TPM handle. If it is not, then /// TBS checks to see if it has a saved context for the entity, and if so loads it. /// 2) If the command will fill a slot, then TBS ensures that a slot is available. It does this by ContextSaving /// the LRU entity of the proper type (that is not used in this command). /// </summary> /// <param name="caller"></param> /// <param name="active"></param> /// <param name="inBuf"></param> /// <param name="outBuf"></param> /// <exception cref="Exception"></exception> internal void DispatchCommand(TbsContext caller, CommandModifier active, byte[] inBuf, out byte[] outBuf) { lock (this) { CommandNumber++; // ReSharper disable once CompareOfFloatsByEqualityOperator if (StateSaveProbability != 0.0) { // S3 debug support DebugStateSave(); LastStateSaveCommandNumber = CommandNumber; } CommandHeader commandHeader; TpmHandle[] inHandles; SessionIn[] inSessions; byte[] commandParmsNoHandles; bool legalCommand = CommandProcessor.CrackCommand(inBuf, out commandHeader, out inHandles, out inSessions, out commandParmsNoHandles); if (!legalCommand) { // Is a diagnostics command. Pass through to TPM (a real RM would refuse). TpmDevice.DispatchCommand(active, inBuf, out outBuf); return; } TpmCc commandCode = commandHeader.CommandCode; // Lookup command CommandInfo command = Tpm2.CommandInfoFromCommandCode(commandCode); if (command == null) { throw new Exception("Unrecognized command"); } if (commandCode == TpmCc.ContextLoad || commandCode == TpmCc.ContextSave) { //throw new Exception("ContextLoad and ContextSave not supported in this build"); Console.Error.WriteLine("ContextLoad and ContextSave not supported in this build"); outBuf = Marshaller.GetTpmRepresentation(new Object[] { TpmSt.NoSessions, (uint)10, TpmRc.NotUsed }); } // Look up referenced objects and sessions ObjectContext[] neededObjects = GetReferencedObjects(caller, inHandles); ObjectContext[] neededSessions = GetSessions(caller, inSessions); if (neededObjects == null || neededSessions == null) { // This means that one or more of the handles was not registered for the context byte[] ret = FormatError(TpmRc.Handle); outBuf = ret; return; } // Load referenced objects and sessions (free slots if needed) bool loadOk = LoadEntities(neededObjects); bool loadOk2 = LoadEntities(neededSessions); if (!loadOk || !loadOk2) { throw new Exception("Failed to make space for objects or sessions at to execute command"); } // At this point everything referenced should be loaded, and there will be a free slot if needed // so we can translate the input handles to the underlying handles ReplaceHandlesIn(inHandles, inSessions, neededObjects, neededSessions); // create the translated command from the various components we have been manipulating byte[] commandBuf = CommandProcessor.CreateCommand(commandHeader.CommandCode, inHandles, inSessions, commandParmsNoHandles); Debug.Assert(commandBuf.Length == inBuf.Length); byte[] responseBuf; // Todo: Virtualize GetCapability for handle enumeration. // // Execute command on underlying TPM device. // If we get an ObjectMemory or SessionMemory error we try to make more space and try again // Note: If the TPM device throws an error above we let it propagate out. There should be no side // effects on TPM state that the TBS cares about. // do { TpmDevice.DispatchCommand(active, commandBuf, out responseBuf); TpmRc resCode = GetResultCode(responseBuf); if (resCode == TpmRc.Success) { break; } if (resCode == TpmRc.ObjectMemory) { bool slotMade = MakeSpace(SlotType.ObjectSlot, neededObjects); if (!slotMade) { throw new Exception("Failed to make an object slot in the TPM"); } continue; } if (resCode == TpmRc.SessionMemory) { bool slotMade = MakeSpace(SlotType.SessionSlot, neededSessions); if (!slotMade) { throw new Exception("Failed to make a session slot in the TPM"); } continue; } break; } while (true); // Parse the response from the TPM // TODO: Make this use the new methods in Tpm2 // ReSharper disable once UnusedVariable var mOut = new Marshaller(responseBuf); TpmSt responseTag; uint responseParamSize; TpmRc resultCode; TpmHandle[] responseHandles; SessionOut[] responseSessions; byte[] responseParmsNoHandles, responseParmsWithHandles; CommandProcessor.SplitResponse(responseBuf, command.HandleCountOut, out responseTag, out responseParamSize, out resultCode, out responseHandles, out responseSessions, out responseParmsNoHandles, out responseParmsWithHandles); // If we have an error there is no impact on the loaded sessions, but we update // the LRU values because the user will likely try again. if (resultCode != TpmRc.Success) { outBuf = responseBuf; UpdateLastUseCount(new[] { neededObjects, neededSessions }); return; } // Update TBS database with any newly created TPM objects ProcessUpdatedTpmState(caller, command, responseHandles, neededObjects); // And if there were any newly created objects use the new DB entries to translate the handles ReplaceHandlesOut(responseHandles); byte[] translatedResponse = CommandProcessor.CreateResponse(resultCode, responseHandles, responseSessions, responseParmsNoHandles); outBuf = translatedResponse; Debug.Assert(outBuf.Length == responseBuf.Length); } // lock(this) }